feedx 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -3
- data/consumer.go +2 -2
- data/consumer_test.go +1 -2
- data/example_test.go +12 -4
- data/feedx.gemspec +1 -1
- data/scheduler.go +25 -22
- data/scheduler_test.go +50 -7
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3586f5580c4f0379b966d324df5dee69f2463ad516842194baf180ee222aad3a
|
|
4
|
+
data.tar.gz: '090b0c6828614705ddefa87d9966ff94ca7a61bab334ad891b2df5850f2100f1'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b35993b35880d79d869fb12d2eed818edc675c0a23ea75ea49af8c488c3ca97bbb71812d50dffbb823ad4a0d437e1108629838c658f30ffe2587358902eb4b5b
|
|
7
|
+
data.tar.gz: 801a0aee9a0899215ab80ba379d8c08956a477cea9179be86dd44e5f140feec5d459e75a230c1d28b0b4b0c2c3719bffacf141abacd02610623c46c47f4a68dc
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
feedx (0.
|
|
4
|
+
feedx (0.15.0)
|
|
5
5
|
bfs (>= 0.8.0)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
@@ -55,9 +55,9 @@ GEM
|
|
|
55
55
|
rubocop-rake (0.7.1)
|
|
56
56
|
lint_roller (~> 1.1)
|
|
57
57
|
rubocop (>= 1.72.1)
|
|
58
|
-
rubocop-rspec (3.
|
|
58
|
+
rubocop-rspec (3.8.0)
|
|
59
59
|
lint_roller (~> 1.1)
|
|
60
|
-
rubocop (~> 1.
|
|
60
|
+
rubocop (~> 1.81)
|
|
61
61
|
ruby-progressbar (1.13.0)
|
|
62
62
|
unicode-display_width (3.2.0)
|
|
63
63
|
unicode-emoji (~> 4.1)
|
data/consumer.go
CHANGED
|
@@ -9,7 +9,7 @@ import (
|
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
// ConsumeFunc is a callback invoked by consumers.
|
|
12
|
-
type ConsumeFunc func(
|
|
12
|
+
type ConsumeFunc func(*Reader) error
|
|
13
13
|
|
|
14
14
|
// Consumer manages data retrieval from a remote feed.
|
|
15
15
|
// It queries the feed in regular intervals, continuously retrieving new updates.
|
|
@@ -106,7 +106,7 @@ func (c *consumer) Consume(ctx context.Context, opt *ReaderOptions, fn ConsumeFu
|
|
|
106
106
|
defer reader.Close()
|
|
107
107
|
|
|
108
108
|
// consume feed
|
|
109
|
-
if err := fn(
|
|
109
|
+
if err := fn(reader); err != nil {
|
|
110
110
|
return nil, err
|
|
111
111
|
}
|
|
112
112
|
|
data/consumer_test.go
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package feedx_test
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"context"
|
|
5
4
|
"reflect"
|
|
6
5
|
"testing"
|
|
7
6
|
|
|
@@ -134,7 +133,7 @@ func fixIncrementalConsumer(t *testing.T, version int64) feedx.Consumer {
|
|
|
134
133
|
func testConsume(t *testing.T, csm feedx.Consumer, exp *feedx.Status) (msgs []*testdata.MockMessage) {
|
|
135
134
|
t.Helper()
|
|
136
135
|
|
|
137
|
-
status, err := csm.Consume(t.Context(), nil, func(
|
|
136
|
+
status, err := csm.Consume(t.Context(), nil, func(r *feedx.Reader) (err error) {
|
|
138
137
|
msgs, err = readMessages(r)
|
|
139
138
|
return err
|
|
140
139
|
})
|
data/example_test.go
CHANGED
|
@@ -43,7 +43,7 @@ func Example() {
|
|
|
43
43
|
|
|
44
44
|
// consume data
|
|
45
45
|
var msgs []*message
|
|
46
|
-
status, err = csm.Consume(context.TODO(), nil, func(
|
|
46
|
+
status, err = csm.Consume(context.TODO(), nil, func(r *feedx.Reader) error {
|
|
47
47
|
for {
|
|
48
48
|
var msg message
|
|
49
49
|
if err := r.Decode(&msg); err != nil {
|
|
@@ -81,7 +81,7 @@ func ExampleScheduler_Consume() {
|
|
|
81
81
|
csm := feedx.NewConsumerForRemote(obj)
|
|
82
82
|
defer csm.Close()
|
|
83
83
|
|
|
84
|
-
job := feedx.Every(time.Hour).
|
|
84
|
+
job, err := feedx.Every(time.Hour).
|
|
85
85
|
WithContext(ctx).
|
|
86
86
|
BeforeSync(func(_ int64) bool {
|
|
87
87
|
fmt.Println("1. Before sync")
|
|
@@ -90,10 +90,14 @@ func ExampleScheduler_Consume() {
|
|
|
90
90
|
AfterSync(func(_ *feedx.Status, err error) {
|
|
91
91
|
fmt.Printf("3. After sync - error:%v", err)
|
|
92
92
|
}).
|
|
93
|
-
Consume(csm, func(_
|
|
93
|
+
Consume(csm, func(_ *feedx.Reader) error {
|
|
94
94
|
fmt.Println("2. Consuming feed")
|
|
95
95
|
return nil
|
|
96
96
|
})
|
|
97
|
+
if err != nil {
|
|
98
|
+
panic(err)
|
|
99
|
+
}
|
|
100
|
+
|
|
97
101
|
job.Stop()
|
|
98
102
|
|
|
99
103
|
// Output:
|
|
@@ -113,7 +117,7 @@ func ExampleScheduler_Produce() {
|
|
|
113
117
|
pcr := feedx.NewProducerForRemote(obj)
|
|
114
118
|
defer pcr.Close()
|
|
115
119
|
|
|
116
|
-
job := feedx.Every(time.Hour).
|
|
120
|
+
job, err := feedx.Every(time.Hour).
|
|
117
121
|
WithContext(ctx).
|
|
118
122
|
BeforeSync(func(_ int64) bool {
|
|
119
123
|
fmt.Println("2. Before sync")
|
|
@@ -130,6 +134,10 @@ func ExampleScheduler_Produce() {
|
|
|
130
134
|
fmt.Println("3. Producing feed")
|
|
131
135
|
return nil
|
|
132
136
|
})
|
|
137
|
+
if err != nil {
|
|
138
|
+
panic(err)
|
|
139
|
+
}
|
|
140
|
+
|
|
133
141
|
job.Stop()
|
|
134
142
|
|
|
135
143
|
// Output:
|
data/feedx.gemspec
CHANGED
data/scheduler.go
CHANGED
|
@@ -60,15 +60,16 @@ func (s *Scheduler) WithReaderOptions(opt *ReaderOptions) *Scheduler {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// Consume starts a consumer job.
|
|
63
|
-
func (s *Scheduler) Consume(csm Consumer, cfn ConsumeFunc) *CronJob {
|
|
64
|
-
return newCronJob(s.ctx, s.interval, func(ctx context.Context) {
|
|
63
|
+
func (s *Scheduler) Consume(csm Consumer, cfn ConsumeFunc) (*CronJob, error) {
|
|
64
|
+
return newCronJob(s.ctx, s.interval, func(ctx context.Context) error {
|
|
65
65
|
version := csm.Version()
|
|
66
66
|
if !s.runBeforeHooks(version) {
|
|
67
|
-
return
|
|
67
|
+
return nil
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
status, err := csm.Consume(ctx, s.readerOpt, cfn)
|
|
71
71
|
s.runAfterHooks(status, err)
|
|
72
|
+
return err
|
|
72
73
|
})
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -85,36 +86,38 @@ func (s *Scheduler) WithVersionCheck(fn VersionCheck) *Scheduler {
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
// Produce starts a producer job.
|
|
88
|
-
func (s *Scheduler) Produce(pcr *Producer, pfn ProduceFunc) *CronJob {
|
|
89
|
+
func (s *Scheduler) Produce(pcr *Producer, pfn ProduceFunc) (*CronJob, error) {
|
|
89
90
|
return s.produce(func(ctx context.Context, version int64) (*Status, error) {
|
|
90
91
|
return pcr.Produce(ctx, version, s.writerOpt, pfn)
|
|
91
92
|
})
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
// ProduceIncrementally starts an incremental producer job.
|
|
95
|
-
func (s *Scheduler) ProduceIncrementally(pcr *IncrementalProducer, pfn IncrementalProduceFunc) *CronJob {
|
|
96
|
+
func (s *Scheduler) ProduceIncrementally(pcr *IncrementalProducer, pfn IncrementalProduceFunc) (*CronJob, error) {
|
|
96
97
|
return s.produce(func(ctx context.Context, version int64) (*Status, error) {
|
|
97
98
|
return pcr.Produce(ctx, version, s.writerOpt, pfn)
|
|
98
99
|
})
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
func (s *Scheduler) produce(fn func(context.Context, int64) (*Status, error)) *CronJob {
|
|
102
|
-
return newCronJob(s.ctx, s.interval, func(ctx context.Context) {
|
|
102
|
+
func (s *Scheduler) produce(fn func(context.Context, int64) (*Status, error)) (*CronJob, error) {
|
|
103
|
+
return newCronJob(s.ctx, s.interval, func(ctx context.Context) error {
|
|
103
104
|
var version int64
|
|
104
105
|
if s.versionCheck != nil {
|
|
105
106
|
latest, err := s.versionCheck(s.ctx)
|
|
106
107
|
if err != nil {
|
|
107
|
-
|
|
108
|
+
s.runAfterHooks(nil, err)
|
|
109
|
+
return err
|
|
108
110
|
}
|
|
109
111
|
version = latest
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
if !s.runBeforeHooks(version) {
|
|
113
|
-
return
|
|
115
|
+
return nil
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
status, err := fn(ctx, version)
|
|
117
119
|
s.runAfterHooks(status, err)
|
|
120
|
+
return err
|
|
118
121
|
})
|
|
119
122
|
}
|
|
120
123
|
|
|
@@ -135,22 +138,21 @@ func (s *Scheduler) runAfterHooks(status *Status, err error) {
|
|
|
135
138
|
|
|
136
139
|
// CronJob runs in regular intervals until it's stopped.
|
|
137
140
|
type CronJob struct {
|
|
138
|
-
ctx context.Context
|
|
139
141
|
cancel context.CancelFunc
|
|
140
142
|
interval time.Duration
|
|
141
|
-
perform func(context.Context)
|
|
143
|
+
perform func(context.Context) error
|
|
142
144
|
wait sync.WaitGroup
|
|
143
145
|
}
|
|
144
146
|
|
|
145
|
-
func newCronJob(ctx context.Context, interval time.Duration, perform func(context.Context)) *CronJob {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
job.perform(ctx) // perform immediately
|
|
147
|
+
func newCronJob(ctx context.Context, interval time.Duration, perform func(context.Context) error) (*CronJob, error) {
|
|
148
|
+
if err := perform(ctx); err != nil {
|
|
149
|
+
return nil, err
|
|
150
|
+
}
|
|
150
151
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
ctx, cancel := context.WithCancel(ctx)
|
|
153
|
+
job := &CronJob{cancel: cancel, interval: interval, perform: perform}
|
|
154
|
+
go job.loop(ctx)
|
|
155
|
+
return job, nil
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
// Stop stops the job and waits until it is complete.
|
|
@@ -159,7 +161,8 @@ func (j *CronJob) Stop() {
|
|
|
159
161
|
j.wait.Wait()
|
|
160
162
|
}
|
|
161
163
|
|
|
162
|
-
func (j *CronJob) loop() {
|
|
164
|
+
func (j *CronJob) loop(ctx context.Context) {
|
|
165
|
+
j.wait.Add(1)
|
|
163
166
|
defer j.wait.Done()
|
|
164
167
|
|
|
165
168
|
ticker := time.NewTicker(j.interval)
|
|
@@ -167,10 +170,10 @@ func (j *CronJob) loop() {
|
|
|
167
170
|
|
|
168
171
|
for {
|
|
169
172
|
select {
|
|
170
|
-
case <-
|
|
173
|
+
case <-ctx.Done():
|
|
171
174
|
return
|
|
172
175
|
case <-ticker.C:
|
|
173
|
-
j.perform(
|
|
176
|
+
_ = j.perform(ctx)
|
|
174
177
|
}
|
|
175
178
|
}
|
|
176
179
|
}
|
data/scheduler_test.go
CHANGED
|
@@ -2,6 +2,7 @@ package feedx_test
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
|
+
"errors"
|
|
5
6
|
"fmt"
|
|
6
7
|
"sync/atomic"
|
|
7
8
|
"testing"
|
|
@@ -17,14 +18,23 @@ func TestScheduler(t *testing.T) {
|
|
|
17
18
|
numCycles := new(atomic.Int32)
|
|
18
19
|
numErrors := new(atomic.Int32)
|
|
19
20
|
|
|
21
|
+
resetCounters := func() {
|
|
22
|
+
beforeCallbacks.Store(0)
|
|
23
|
+
afterCallbacks.Store(0)
|
|
24
|
+
numCycles.Store(0)
|
|
25
|
+
numErrors.Store(0)
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
obj := bfs.NewInMemObject("file.json")
|
|
21
29
|
defer obj.Close()
|
|
22
30
|
|
|
23
31
|
t.Run("produce", func(t *testing.T) {
|
|
32
|
+
resetCounters()
|
|
33
|
+
|
|
24
34
|
pcr := feedx.NewProducerForRemote(obj)
|
|
25
35
|
defer pcr.Close()
|
|
26
36
|
|
|
27
|
-
job := feedx.Every(time.Millisecond).
|
|
37
|
+
job, err := feedx.Every(time.Millisecond).
|
|
28
38
|
BeforeSync(func(_ int64) bool {
|
|
29
39
|
beforeCallbacks.Add(1)
|
|
30
40
|
return true
|
|
@@ -45,6 +55,9 @@ func TestScheduler(t *testing.T) {
|
|
|
45
55
|
}
|
|
46
56
|
return nil
|
|
47
57
|
})
|
|
58
|
+
if err != nil {
|
|
59
|
+
t.Fatal("unexpected error", err)
|
|
60
|
+
}
|
|
48
61
|
|
|
49
62
|
time.Sleep(5 * time.Millisecond)
|
|
50
63
|
job.Stop()
|
|
@@ -71,17 +84,29 @@ func TestScheduler(t *testing.T) {
|
|
|
71
84
|
}
|
|
72
85
|
})
|
|
73
86
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
87
|
+
t.Run("produce may fail", func(t *testing.T) {
|
|
88
|
+
resetCounters()
|
|
89
|
+
|
|
90
|
+
pcr := feedx.NewProducerForRemote(obj)
|
|
91
|
+
defer pcr.Close()
|
|
92
|
+
|
|
93
|
+
exp := fmt.Errorf("failed!")
|
|
94
|
+
_, err := feedx.Every(time.Millisecond).
|
|
95
|
+
Produce(pcr, func(w *feedx.Writer) error {
|
|
96
|
+
return exp
|
|
97
|
+
})
|
|
98
|
+
if !errors.Is(err, exp) {
|
|
99
|
+
t.Errorf("expected %v, got %v", exp, err)
|
|
100
|
+
}
|
|
101
|
+
})
|
|
78
102
|
|
|
79
103
|
t.Run("consume", func(t *testing.T) {
|
|
104
|
+
resetCounters()
|
|
80
105
|
|
|
81
106
|
csm := feedx.NewConsumerForRemote(obj)
|
|
82
107
|
defer csm.Close()
|
|
83
108
|
|
|
84
|
-
job := feedx.Every(time.Millisecond).
|
|
109
|
+
job, err := feedx.Every(time.Millisecond).
|
|
85
110
|
BeforeSync(func(_ int64) bool {
|
|
86
111
|
beforeCallbacks.Add(1)
|
|
87
112
|
return true
|
|
@@ -93,12 +118,15 @@ func TestScheduler(t *testing.T) {
|
|
|
93
118
|
numErrors.Add(1)
|
|
94
119
|
}
|
|
95
120
|
}).
|
|
96
|
-
Consume(csm, func(
|
|
121
|
+
Consume(csm, func(r *feedx.Reader) error {
|
|
97
122
|
if numCycles.Add(1)%2 == 0 {
|
|
98
123
|
return fmt.Errorf("failed!")
|
|
99
124
|
}
|
|
100
125
|
return nil
|
|
101
126
|
})
|
|
127
|
+
if err != nil {
|
|
128
|
+
t.Fatal("unexpected error", err)
|
|
129
|
+
}
|
|
102
130
|
|
|
103
131
|
time.Sleep(5 * time.Millisecond)
|
|
104
132
|
job.Stop()
|
|
@@ -125,4 +153,19 @@ func TestScheduler(t *testing.T) {
|
|
|
125
153
|
}
|
|
126
154
|
})
|
|
127
155
|
|
|
156
|
+
t.Run("consume may fail", func(t *testing.T) {
|
|
157
|
+
resetCounters()
|
|
158
|
+
|
|
159
|
+
csm := feedx.NewConsumerForRemote(obj)
|
|
160
|
+
defer csm.Close()
|
|
161
|
+
|
|
162
|
+
exp := fmt.Errorf("failed!")
|
|
163
|
+
_, err := feedx.Every(time.Millisecond).
|
|
164
|
+
Consume(csm, func(r *feedx.Reader) error {
|
|
165
|
+
return exp
|
|
166
|
+
})
|
|
167
|
+
if !errors.Is(err, exp) {
|
|
168
|
+
t.Errorf("expected %v, got %v", exp, err)
|
|
169
|
+
}
|
|
170
|
+
})
|
|
128
171
|
}
|