Skip to content

Commit 1bce146

Browse files
committed
improved error handling in On listener + tests
1 parent 7424cde commit 1bce146

File tree

4 files changed

+92
-15
lines changed

4 files changed

+92
-15
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.19
44

55
require (
66
github.com/go-redis/redis/v8 v8.11.5
7+
github.com/mitchellh/mapstructure v1.5.0
78
github.com/stretchr/testify v1.8.1
89
)
910

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu
88
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
99
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
1010
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
11+
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
12+
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
1113
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
1214
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
1315
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=

schedule.go

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ import (
1111
)
1212

1313
const redisNamespace = "boomerang"
14+
1415
var (
15-
ErrUnexpectedReturnCodeFromRedis = errors.New("unexpected return code from redis")
16-
ErrTaskAlreadyExists = errors.New("task already exists")
17-
ErrTaskDoesNotExist = errors.New("task does not exist")
16+
ErrUnexpectedReturnCode = errors.New("unexpected return code from redis")
17+
ErrUnexpectedReturnCodeType = errors.New("unexpected return code type from redis, expect integer")
18+
ErrTaskAlreadyExists = errors.New("task already exists")
19+
ErrTaskDoesNotExist = errors.New("task does not exist")
20+
ErrTaskDataDoesNotExist = errors.New("task data does not exist")
21+
ErrTaskDataInvalidFormat = errors.New("task data has invalid format, expected JSON")
1822
)
1923

2024
type Schedule interface {
@@ -96,7 +100,7 @@ func (s *ScheduleImpl) Add(ctx context.Context, task *Task) error {
96100
case -1:
97101
return ErrTaskAlreadyExists
98102
default:
99-
return ErrUnexpectedReturnCodeFromRedis
103+
return ErrUnexpectedReturnCode
100104
}
101105
}
102106

@@ -160,7 +164,7 @@ func (s *ScheduleImpl) Update(ctx context.Context, task *Task) error {
160164
case -1:
161165
return ErrTaskDoesNotExist
162166
default:
163-
return ErrUnexpectedReturnCodeFromRedis
167+
return ErrUnexpectedReturnCode
164168
}
165169
}
166170

@@ -203,7 +207,7 @@ func (s *ScheduleImpl) Remove(ctx context.Context, kind string, id string) error
203207
case -1:
204208
return ErrTaskDoesNotExist
205209
default:
206-
return ErrUnexpectedReturnCodeFromRedis
210+
return ErrUnexpectedReturnCode
207211
}
208212
}
209213

@@ -250,7 +254,7 @@ func (s *ScheduleImpl) RunNow(ctx context.Context, kind string, id string) error
250254
case -1:
251255
return ErrTaskDoesNotExist
252256
default:
253-
return ErrUnexpectedReturnCodeFromRedis
257+
return ErrUnexpectedReturnCode
254258
}
255259
}
256260

@@ -267,6 +271,7 @@ func (s *ScheduleImpl) On(ctx context.Context, kind string, handler func(ctx con
267271
268272
local res = redis.call("ZPOPMIN", queueKey)
269273
if #res == 0 then
274+
-- Error: No tasks scheduled
270275
return { -1 }
271276
end
272277
@@ -277,19 +282,23 @@ func (s *ScheduleImpl) On(ctx context.Context, kind string, handler func(ctx con
277282
278283
if score > (now + 1000) then
279284
redis.call("ZADD", queueKey, score, id)
285+
286+
-- Error: Next task is scheduled for more than 1 second in the future
280287
return { -1 }
281288
end
282289
283290
-- Get the task data
284291
285292
local taskDataRaw = redis.call("HGET", taskDataKey, id)
286293
if taskDataRaw == nil then
287-
return { -1 }
294+
-- Error: task data does not exist
295+
return { -2 }
288296
end
289297
290298
local taskData = cjson.decode(taskDataRaw)
291299
if taskData == nil then
292-
return { -1 }
300+
-- Error: task data has invalid format
301+
return { -3 }
293302
end
294303
295304
-- Schedule the next execution
@@ -335,21 +344,38 @@ func (s *ScheduleImpl) On(ctx context.Context, kind string, handler func(ctx con
335344
return err
336345
}
337346

338-
if len(resSlice) != 3 {
347+
code, ok := resSlice[0].(int64)
348+
if !ok {
349+
return ErrUnexpectedReturnCodeType
350+
}
351+
352+
if code == -1 {
339353
select {
340354
case <-ctx.Done():
341355
return ctx.Err()
342-
case <-time.After(1 * time.Second):
356+
case <-time.After(time.Second):
343357
continue
344358
}
345359
}
346360

347-
id, ok := resSlice[0].(string)
361+
if code == -2 {
362+
return ErrTaskDataDoesNotExist
363+
}
364+
365+
if code == -3 {
366+
return ErrTaskDataInvalidFormat
367+
}
368+
369+
if code != 0 {
370+
return ErrUnexpectedReturnCode
371+
}
372+
373+
id, ok := resSlice[1].(string)
348374
if !ok {
349375
return errors.New("unexpected type for id")
350376
}
351377

352-
score, ok := resSlice[1].(int64)
378+
score, ok := resSlice[2].(int64)
353379
if !ok {
354380
return errors.New("unexpected type for score")
355381
}
@@ -359,7 +385,7 @@ func (s *ScheduleImpl) On(ctx context.Context, kind string, handler func(ctx con
359385
time.Sleep(time.Duration(delta) * time.Millisecond)
360386
}
361387

362-
taskDataRaw, ok := resSlice[2].(string)
388+
taskDataRaw, ok := resSlice[3].(string)
363389
if !ok {
364390
return errors.New("unexpected type for taskDataRaw")
365391
}

schedule_test.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
var testTask1 = NewTask(
1313
"test",
1414
"id",
15-
1*time.Second,
15+
10*time.Millisecond,
1616
map[string]any{
1717
"foo": "bar",
1818
},
@@ -78,3 +78,51 @@ func TestScheduleImpl_Remove(t *testing.T) {
7878
err = schedule.Remove(ctx, testTask1.Kind, testTask1.ID)
7979
assert.NoError(t, err)
8080
}
81+
82+
func TestScheduleImpl_RunNow(t *testing.T) {
83+
t.Parallel()
84+
85+
ctx := context.Background()
86+
87+
schedule := newSchedule(t, ctx, 4)
88+
89+
err := schedule.RunNow(ctx, testTask1.Kind, testTask1.ID)
90+
assert.ErrorIs(t, err, ErrTaskDoesNotExist)
91+
92+
err = schedule.Add(ctx, testTask1)
93+
assert.NoError(t, err)
94+
95+
err = schedule.RunNow(ctx, testTask1.Kind, testTask1.ID)
96+
assert.NoError(t, err)
97+
}
98+
99+
func TestScheduleImpl_On(t *testing.T) {
100+
t.Parallel()
101+
102+
ctx := context.Background()
103+
104+
schedule := newSchedule(t, ctx, 5)
105+
106+
// Test receiving a task.
107+
108+
ctxA, cancelA := context.WithTimeout(ctx, 1*time.Second)
109+
110+
err := schedule.Add(ctx, testTask1)
111+
assert.NoError(t, err)
112+
113+
err = schedule.On(ctxA, testTask1.Kind, func(ctx context.Context, task *Task) {
114+
cancelA()
115+
})
116+
117+
assert.ErrorIs(t, err, context.Canceled)
118+
119+
// Test never receiving a task because it is of the wrong kind.
120+
121+
ctxB, cancelB := context.WithTimeout(ctx, 100*time.Millisecond)
122+
123+
err = schedule.On(ctxB, "unknown", func(ctx context.Context, task *Task) {
124+
cancelB()
125+
})
126+
127+
assert.ErrorIs(t, err, context.DeadlineExceeded)
128+
}

0 commit comments

Comments
 (0)