Merge branch 'v2' into dependabot/go_modules/github.com/stretchr/testify-1.11.0

This commit is contained in:
John Roesler 2025-08-27 11:03:56 -05:00 committed by GitHub
commit 755febc83e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 198 additions and 46 deletions

142
.github/copilot-instructions.md vendored Normal file
View File

@ -0,0 +1,142 @@
# gocron: Go Job Scheduling Library
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
## Working Effectively
### Bootstrap and Build Commands
- Install dependencies: `go mod tidy`
- Build the library: `go build -v ./...`
- Install required tools:
- `go install go.uber.org/mock/mockgen@latest`
- `export PATH=$PATH:$(go env GOPATH)/bin` (add to shell profile)
- Generate mocks: `make mocks`
- Format code: `make fmt`
### Testing Commands
- Run all tests: `make test` -- takes 50 seconds. NEVER CANCEL. Set timeout to 90+ seconds.
- Run CI tests: `make test_ci` -- takes 50 seconds. NEVER CANCEL. Set timeout to 90+ seconds.
- Run with coverage: `make test_coverage` -- takes 50 seconds. NEVER CANCEL. Set timeout to 90+ seconds.
- Run specific tests: `go test -v -race -count=1 ./...`
### Linting Commands
- Format verification: `grep "^func [a-zA-Z]" example_test.go | sort -c`
- Full linting: `make lint` -- MAY FAIL due to golangci-lint config compatibility issues. This is a known issue.
- Alternative basic linting: `go vet ./...` and `gofmt -d .`
## Validation
### Required Validation Steps
- ALWAYS run `make test` before submitting changes. Tests must pass.
- ALWAYS run `make fmt` to ensure proper formatting.
- ALWAYS run `make mocks` if you change interface definitions.
- ALWAYS verify examples still work by running them: `cd examples/elector && go run main.go`
### Manual Testing Scenarios
Since this is a library, not an application, testing involves:
1. **Basic Scheduler Creation**: Verify you can create a scheduler with `gocron.NewScheduler()`
2. **Job Creation**: Verify you can create jobs with various `JobDefinition` types
3. **Scheduler Lifecycle**: Verify Start() and Shutdown() work correctly
4. **Example Validation**: Run examples in `examples/` directory to ensure functionality
Example validation script:
```go
package main
import (
"fmt"
"time"
"github.com/go-co-op/gocron/v2"
)
func main() {
s, err := gocron.NewScheduler()
if err != nil { panic(err) }
j, err := s.NewJob(
gocron.DurationJob(2*time.Second),
gocron.NewTask(func() { fmt.Println("Working!") }),
)
if err != nil { panic(err) }
fmt.Printf("Job ID: %s\n", j.ID())
s.Start()
time.Sleep(6 * time.Second)
s.Shutdown()
}
```
### CI Requirements
The CI will fail if:
- Tests don't pass (`make test_ci`)
- Function order in `example_test.go` is incorrect
- golangci-lint finds issues (though config compatibility varies)
## Common Tasks
### Repository Structure
```
.
├── README.md # Main documentation
├── CONTRIBUTING.md # Contribution guidelines
├── SECURITY.md # Security policy
├── Makefile # Build automation
├── go.mod # Go module definition
├── .github/ # GitHub workflows and configs
├── .golangci.yaml # Linting configuration
├── examples/ # Usage examples
│ └── elector/ # Distributed elector example
├── mocks/ # Generated mock files
├── *.go # Library source files
└── *_test.go # Test files
```
### Key Source Files
- `scheduler.go` - Main scheduler implementation
- `job.go` - Job definitions and scheduling logic
- `executor.go` - Job execution engine
- `logger.go` - Logging interfaces and implementations
- `distributed.go` - Distributed scheduling support
- `monitor.go` - Job monitoring interfaces
- `util.go` - Utility functions
- `errors.go` - Error definitions
### Dependencies and Versions
- Requires Go 1.23.0+
- Key dependencies automatically managed via `go mod`:
- `github.com/google/uuid` - UUID generation
- `github.com/jonboulle/clockwork` - Time mocking for tests
- `github.com/robfig/cron/v3` - Cron expression parsing
- `github.com/stretchr/testify` - Testing utilities
- `go.uber.org/goleak` - Goroutine leak detection
### Testing Patterns
- Uses table-driven tests following Go best practices
- Extensive use of goroutine leak detection (may be skipped in CI via TEST_ENV)
- Mock-based testing for interfaces
- Race condition detection enabled (`-race` flag)
- 93.8% test coverage expected
### Build and Release
- No application to build - this is a library
- Version managed via Git tags (v2.x.x)
- Distribution via Go module system
- CI tests on Go 1.23 and 1.24
## Troubleshooting
### Common Issues
1. **mockgen not found**: Install with `go install go.uber.org/mock/mockgen@latest`
2. **golangci-lint config errors**: Known compatibility issue - use `go vet` instead
3. **Test timeouts**: Tests can take 50+ seconds, always set adequate timeouts
4. **PATH issues**: Ensure `$(go env GOPATH)/bin` is in PATH
5. **Import errors in examples**: Run `go mod tidy` to resolve dependencies
### Expected Timings
- `make test`: ~50 seconds
- `make test_coverage`: ~50 seconds
- `make test_ci`: ~50 seconds
- `go build`: ~5 seconds
- `make mocks`: ~2 seconds
- `make fmt`: <1 second
### Known Limitations
- golangci-lint configuration may have compatibility issues with certain versions
- Some tests are skipped in CI environments (controlled by TEST_ENV variable)
- Examples directory has no tests but should be manually validated

View File

@ -29,7 +29,7 @@ type executor struct {
// sends out jobs once completed // sends out jobs once completed
jobsOutCompleted chan uuid.UUID jobsOutCompleted chan uuid.UUID
// used to request jobs from the scheduler // used to request jobs from the scheduler
jobOutRequest chan jobOutRequest jobOutRequest chan *jobOutRequest
// sends out job needs to update the next runs // sends out job needs to update the next runs
jobUpdateNextRuns chan uuid.UUID jobUpdateNextRuns chan uuid.UUID

2
job.go
View File

@ -1136,7 +1136,7 @@ type job struct {
id uuid.UUID id uuid.UUID
name string name string
tags []string tags []string
jobOutRequest chan jobOutRequest jobOutRequest chan *jobOutRequest
runJobRequest chan runJobRequest runJobRequest chan runJobRequest
} }

View File

@ -5,6 +5,7 @@
// //
// mockgen -destination=mocks/distributed.go -package=gocronmocks . Elector,Locker,Lock // mockgen -destination=mocks/distributed.go -package=gocronmocks . Elector,Locker,Lock
// //
// Package gocronmocks is a generated GoMock package. // Package gocronmocks is a generated GoMock package.
package gocronmocks package gocronmocks
@ -12,7 +13,7 @@ import (
context "context" context "context"
reflect "reflect" reflect "reflect"
gocron "github.com/go-co-op/gocron/v2" v2 "github.com/go-co-op/gocron/v2"
gomock "go.uber.org/mock/gomock" gomock "go.uber.org/mock/gomock"
) )
@ -20,6 +21,7 @@ import (
type MockElector struct { type MockElector struct {
ctrl *gomock.Controller ctrl *gomock.Controller
recorder *MockElectorMockRecorder recorder *MockElectorMockRecorder
isgomock struct{}
} }
// MockElectorMockRecorder is the mock recorder for MockElector. // MockElectorMockRecorder is the mock recorder for MockElector.
@ -57,6 +59,7 @@ func (mr *MockElectorMockRecorder) IsLeader(arg0 any) *gomock.Call {
type MockLocker struct { type MockLocker struct {
ctrl *gomock.Controller ctrl *gomock.Controller
recorder *MockLockerMockRecorder recorder *MockLockerMockRecorder
isgomock struct{}
} }
// MockLockerMockRecorder is the mock recorder for MockLocker. // MockLockerMockRecorder is the mock recorder for MockLocker.
@ -77,24 +80,25 @@ func (m *MockLocker) EXPECT() *MockLockerMockRecorder {
} }
// Lock mocks base method. // Lock mocks base method.
func (m *MockLocker) Lock(arg0 context.Context, arg1 string) (gocron.Lock, error) { func (m *MockLocker) Lock(ctx context.Context, key string) (v2.Lock, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Lock", arg0, arg1) ret := m.ctrl.Call(m, "Lock", ctx, key)
ret0, _ := ret[0].(gocron.Lock) ret0, _ := ret[0].(v2.Lock)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// Lock indicates an expected call of Lock. // Lock indicates an expected call of Lock.
func (mr *MockLockerMockRecorder) Lock(arg0, arg1 any) *gomock.Call { func (mr *MockLockerMockRecorder) Lock(ctx, key any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockLocker)(nil).Lock), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockLocker)(nil).Lock), ctx, key)
} }
// MockLock is a mock of Lock interface. // MockLock is a mock of Lock interface.
type MockLock struct { type MockLock struct {
ctrl *gomock.Controller ctrl *gomock.Controller
recorder *MockLockMockRecorder recorder *MockLockMockRecorder
isgomock struct{}
} }
// MockLockMockRecorder is the mock recorder for MockLock. // MockLockMockRecorder is the mock recorder for MockLock.
@ -115,15 +119,15 @@ func (m *MockLock) EXPECT() *MockLockMockRecorder {
} }
// Unlock mocks base method. // Unlock mocks base method.
func (m *MockLock) Unlock(arg0 context.Context) error { func (m *MockLock) Unlock(ctx context.Context) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Unlock", arg0) ret := m.ctrl.Call(m, "Unlock", ctx)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// Unlock indicates an expected call of Unlock. // Unlock indicates an expected call of Unlock.
func (mr *MockLockMockRecorder) Unlock(arg0 any) *gomock.Call { func (mr *MockLockMockRecorder) Unlock(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockLock)(nil).Unlock), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockLock)(nil).Unlock), ctx)
} }

View File

@ -5,6 +5,7 @@
// //
// mockgen -destination=mocks/job.go -package=gocronmocks . Job // mockgen -destination=mocks/job.go -package=gocronmocks . Job
// //
// Package gocronmocks is a generated GoMock package. // Package gocronmocks is a generated GoMock package.
package gocronmocks package gocronmocks
@ -20,6 +21,7 @@ import (
type MockJob struct { type MockJob struct {
ctrl *gomock.Controller ctrl *gomock.Controller
recorder *MockJobMockRecorder recorder *MockJobMockRecorder
isgomock struct{}
} }
// MockJobMockRecorder is the mock recorder for MockJob. // MockJobMockRecorder is the mock recorder for MockJob.

View File

@ -5,6 +5,7 @@
// //
// mockgen -destination=mocks/logger.go -package=gocronmocks . Logger // mockgen -destination=mocks/logger.go -package=gocronmocks . Logger
// //
// Package gocronmocks is a generated GoMock package. // Package gocronmocks is a generated GoMock package.
package gocronmocks package gocronmocks
@ -18,6 +19,7 @@ import (
type MockLogger struct { type MockLogger struct {
ctrl *gomock.Controller ctrl *gomock.Controller
recorder *MockLoggerMockRecorder recorder *MockLoggerMockRecorder
isgomock struct{}
} }
// MockLoggerMockRecorder is the mock recorder for MockLogger. // MockLoggerMockRecorder is the mock recorder for MockLogger.
@ -38,69 +40,69 @@ func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
} }
// Debug mocks base method. // Debug mocks base method.
func (m *MockLogger) Debug(arg0 string, arg1 ...any) { func (m *MockLogger) Debug(msg string, args ...any) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
varargs := []any{arg0} varargs := []any{msg}
for _, a := range arg1 { for _, a := range args {
varargs = append(varargs, a) varargs = append(varargs, a)
} }
m.ctrl.Call(m, "Debug", varargs...) m.ctrl.Call(m, "Debug", varargs...)
} }
// Debug indicates an expected call of Debug. // Debug indicates an expected call of Debug.
func (mr *MockLoggerMockRecorder) Debug(arg0 any, arg1 ...any) *gomock.Call { func (mr *MockLoggerMockRecorder) Debug(msg any, args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0}, arg1...) varargs := append([]any{msg}, args...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockLogger)(nil).Debug), varargs...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockLogger)(nil).Debug), varargs...)
} }
// Error mocks base method. // Error mocks base method.
func (m *MockLogger) Error(arg0 string, arg1 ...any) { func (m *MockLogger) Error(msg string, args ...any) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
varargs := []any{arg0} varargs := []any{msg}
for _, a := range arg1 { for _, a := range args {
varargs = append(varargs, a) varargs = append(varargs, a)
} }
m.ctrl.Call(m, "Error", varargs...) m.ctrl.Call(m, "Error", varargs...)
} }
// Error indicates an expected call of Error. // Error indicates an expected call of Error.
func (mr *MockLoggerMockRecorder) Error(arg0 any, arg1 ...any) *gomock.Call { func (mr *MockLoggerMockRecorder) Error(msg any, args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0}, arg1...) varargs := append([]any{msg}, args...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), varargs...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), varargs...)
} }
// Info mocks base method. // Info mocks base method.
func (m *MockLogger) Info(arg0 string, arg1 ...any) { func (m *MockLogger) Info(msg string, args ...any) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
varargs := []any{arg0} varargs := []any{msg}
for _, a := range arg1 { for _, a := range args {
varargs = append(varargs, a) varargs = append(varargs, a)
} }
m.ctrl.Call(m, "Info", varargs...) m.ctrl.Call(m, "Info", varargs...)
} }
// Info indicates an expected call of Info. // Info indicates an expected call of Info.
func (mr *MockLoggerMockRecorder) Info(arg0 any, arg1 ...any) *gomock.Call { func (mr *MockLoggerMockRecorder) Info(msg any, args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0}, arg1...) varargs := append([]any{msg}, args...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), varargs...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), varargs...)
} }
// Warn mocks base method. // Warn mocks base method.
func (m *MockLogger) Warn(arg0 string, arg1 ...any) { func (m *MockLogger) Warn(msg string, args ...any) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
varargs := []any{arg0} varargs := []any{msg}
for _, a := range arg1 { for _, a := range args {
varargs = append(varargs, a) varargs = append(varargs, a)
} }
m.ctrl.Call(m, "Warn", varargs...) m.ctrl.Call(m, "Warn", varargs...)
} }
// Warn indicates an expected call of Warn. // Warn indicates an expected call of Warn.
func (mr *MockLoggerMockRecorder) Warn(arg0 any, arg1 ...any) *gomock.Call { func (mr *MockLoggerMockRecorder) Warn(msg any, args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0}, arg1...) varargs := append([]any{msg}, args...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), varargs...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), varargs...)
} }

View File

@ -5,13 +5,14 @@
// //
// mockgen -destination=mocks/scheduler.go -package=gocronmocks . Scheduler // mockgen -destination=mocks/scheduler.go -package=gocronmocks . Scheduler
// //
// Package gocronmocks is a generated GoMock package. // Package gocronmocks is a generated GoMock package.
package gocronmocks package gocronmocks
import ( import (
reflect "reflect" reflect "reflect"
gocron "github.com/go-co-op/gocron/v2" v2 "github.com/go-co-op/gocron/v2"
uuid "github.com/google/uuid" uuid "github.com/google/uuid"
gomock "go.uber.org/mock/gomock" gomock "go.uber.org/mock/gomock"
) )
@ -20,6 +21,7 @@ import (
type MockScheduler struct { type MockScheduler struct {
ctrl *gomock.Controller ctrl *gomock.Controller
recorder *MockSchedulerMockRecorder recorder *MockSchedulerMockRecorder
isgomock struct{}
} }
// MockSchedulerMockRecorder is the mock recorder for MockScheduler. // MockSchedulerMockRecorder is the mock recorder for MockScheduler.
@ -40,10 +42,10 @@ func (m *MockScheduler) EXPECT() *MockSchedulerMockRecorder {
} }
// Jobs mocks base method. // Jobs mocks base method.
func (m *MockScheduler) Jobs() []gocron.Job { func (m *MockScheduler) Jobs() []v2.Job {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Jobs") ret := m.ctrl.Call(m, "Jobs")
ret0, _ := ret[0].([]gocron.Job) ret0, _ := ret[0].([]v2.Job)
return ret0 return ret0
} }
@ -68,14 +70,14 @@ func (mr *MockSchedulerMockRecorder) JobsWaitingInQueue() *gomock.Call {
} }
// NewJob mocks base method. // NewJob mocks base method.
func (m *MockScheduler) NewJob(arg0 gocron.JobDefinition, arg1 gocron.Task, arg2 ...gocron.JobOption) (gocron.Job, error) { func (m *MockScheduler) NewJob(arg0 v2.JobDefinition, arg1 v2.Task, arg2 ...v2.JobOption) (v2.Job, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
varargs := []any{arg0, arg1} varargs := []any{arg0, arg1}
for _, a := range arg2 { for _, a := range arg2 {
varargs = append(varargs, a) varargs = append(varargs, a)
} }
ret := m.ctrl.Call(m, "NewJob", varargs...) ret := m.ctrl.Call(m, "NewJob", varargs...)
ret0, _ := ret[0].(gocron.Job) ret0, _ := ret[0].(v2.Job)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
@ -158,14 +160,14 @@ func (mr *MockSchedulerMockRecorder) StopJobs() *gomock.Call {
} }
// Update mocks base method. // Update mocks base method.
func (m *MockScheduler) Update(arg0 uuid.UUID, arg1 gocron.JobDefinition, arg2 gocron.Task, arg3 ...gocron.JobOption) (gocron.Job, error) { func (m *MockScheduler) Update(arg0 uuid.UUID, arg1 v2.JobDefinition, arg2 v2.Task, arg3 ...v2.JobOption) (v2.Job, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
varargs := []any{arg0, arg1, arg2} varargs := []any{arg0, arg1, arg2}
for _, a := range arg3 { for _, a := range arg3 {
varargs = append(varargs, a) varargs = append(varargs, a)
} }
ret := m.ctrl.Call(m, "Update", varargs...) ret := m.ctrl.Call(m, "Update", varargs...)
ret0, _ := ret[0].(gocron.Job) ret0, _ := ret[0].(v2.Job)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }

View File

@ -90,7 +90,7 @@ type scheduler struct {
// used to send all the jobs out when a request is made by the client // used to send all the jobs out when a request is made by the client
allJobsOutRequest chan allJobsOutRequest allJobsOutRequest chan allJobsOutRequest
// used to send a jobs out when a request is made by the client // used to send a jobs out when a request is made by the client
jobOutRequestCh chan jobOutRequest jobOutRequestCh chan *jobOutRequest
// used to run a job on-demand when requested by the client // used to run a job on-demand when requested by the client
runJobRequestCh chan runJobRequest runJobRequestCh chan runJobRequest
// new jobs are received here // new jobs are received here
@ -140,7 +140,7 @@ func NewScheduler(options ...SchedulerOption) (Scheduler, error) {
jobsOutForRescheduling: make(chan uuid.UUID), jobsOutForRescheduling: make(chan uuid.UUID),
jobUpdateNextRuns: make(chan uuid.UUID), jobUpdateNextRuns: make(chan uuid.UUID),
jobsOutCompleted: make(chan uuid.UUID), jobsOutCompleted: make(chan uuid.UUID),
jobOutRequest: make(chan jobOutRequest, 1000), jobOutRequest: make(chan *jobOutRequest, 100),
done: make(chan error, 1), done: make(chan error, 1),
} }
@ -159,7 +159,7 @@ func NewScheduler(options ...SchedulerOption) (Scheduler, error) {
startedCh: make(chan struct{}), startedCh: make(chan struct{}),
stopCh: make(chan struct{}), stopCh: make(chan struct{}),
stopErrCh: make(chan error, 1), stopErrCh: make(chan error, 1),
jobOutRequestCh: make(chan jobOutRequest), jobOutRequestCh: make(chan *jobOutRequest),
runJobRequestCh: make(chan runJobRequest), runJobRequestCh: make(chan runJobRequest),
allJobsOutRequest: make(chan allJobsOutRequest), allJobsOutRequest: make(chan allJobsOutRequest),
} }
@ -461,7 +461,7 @@ func (s *scheduler) selectExecJobsOutCompleted(id uuid.UUID) {
s.jobs[id] = j s.jobs[id] = j
} }
func (s *scheduler) selectJobOutRequest(out jobOutRequest) { func (s *scheduler) selectJobOutRequest(out *jobOutRequest) {
if j, ok := s.jobs[out.id]; ok { if j, ok := s.jobs[out.id]; ok {
select { select {
case out.outChan <- j: case out.outChan <- j:

View File

@ -35,16 +35,16 @@ func callJobFuncWithParams(jobFunc any, params ...any) error {
return nil return nil
} }
func requestJob(id uuid.UUID, ch chan jobOutRequest) *internalJob { func requestJob(id uuid.UUID, ch chan *jobOutRequest) *internalJob {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
return requestJobCtx(ctx, id, ch) return requestJobCtx(ctx, id, ch)
} }
func requestJobCtx(ctx context.Context, id uuid.UUID, ch chan jobOutRequest) *internalJob { func requestJobCtx(ctx context.Context, id uuid.UUID, ch chan *jobOutRequest) *internalJob {
resp := make(chan internalJob, 1) resp := make(chan internalJob, 1)
select { select {
case ch <- jobOutRequest{ case ch <- &jobOutRequest{
id: id, id: id,
outChan: resp, outChan: resp,
}: }: