delayer 0.0.2 → 1.0.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 +7 -0
- data/.gitignore +1 -0
- data/delayer.gemspec +5 -3
- data/lib/delayer.rb +7 -29
- data/lib/delayer/error.rb +5 -0
- data/lib/delayer/extend.rb +93 -12
- data/lib/delayer/version.rb +1 -1
- data/test/test_delayer.rb +40 -1
- data/test/test_priority.rb +1 -1
- metadata +46 -24
- data/lib/delayer/priority.rb +0 -84
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 40812fa4ace5f5b6bf079c393927bc028e466720edbd9b7e5bbd09b224fe9fb5
|
4
|
+
data.tar.gz: a07eca82e3a3bdb77ca4fbd6dc957d871ee9da591436ebedf14a88c46cfaf924
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: de7563e47139c0d59ab569114fc6f03234bab94fbdc8af27ba6dc3d0bea3aa87e04f64aebb5d97ac0f0019c3dab2499e772a43934ce2c1faa168da454cf10692
|
7
|
+
data.tar.gz: 370139c813d0f04d00ef1c5a5c69b034a033bdb7a70999a5c1d38c0c536064d037312025a0dd5bd88c843325c3819a95a3ca1c97ca685e4ee08f90123568c701
|
data/.gitignore
CHANGED
data/delayer.gemspec
CHANGED
@@ -10,14 +10,16 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["toshi.alternative@gmail.com"]
|
11
11
|
spec.description = %q{Delay the processing}
|
12
12
|
spec.summary = %q{Delay the processing}
|
13
|
-
spec.homepage = ""
|
13
|
+
spec.homepage = "https://github.com/toshia/delayer"
|
14
14
|
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = '>= 2.4.0'
|
15
16
|
|
16
17
|
spec.files = `git ls-files`.split($/)
|
17
18
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
20
|
spec.require_paths = ["lib"]
|
20
21
|
|
21
|
-
spec.add_development_dependency "bundler"
|
22
|
-
spec.add_development_dependency "rake"
|
22
|
+
spec.add_development_dependency "bundler"
|
23
|
+
spec.add_development_dependency "rake", '>= 12.3.2'
|
24
|
+
spec.add_development_dependency "test-unit", '>= 3.3.3', '< 4.0'
|
23
25
|
end
|
data/lib/delayer.rb
CHANGED
@@ -3,17 +3,12 @@ require "delayer/version"
|
|
3
3
|
require "delayer/error"
|
4
4
|
require "delayer/extend"
|
5
5
|
require "delayer/procedure"
|
6
|
-
require "delayer/priority"
|
7
6
|
require "monitor"
|
8
7
|
|
9
8
|
module Delayer
|
10
9
|
class << self
|
11
10
|
attr_accessor :default
|
12
11
|
|
13
|
-
def included(klass)
|
14
|
-
klass.extend Extend
|
15
|
-
end
|
16
|
-
|
17
12
|
# Generate new Delayer class.
|
18
13
|
# ==== Args
|
19
14
|
# [options]
|
@@ -24,17 +19,15 @@ module Delayer
|
|
24
19
|
# ==== Return
|
25
20
|
# A new class
|
26
21
|
def generate_class(options = {})
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
22
|
+
Class.new do
|
23
|
+
include ::Delayer
|
24
|
+
@expire = options[:expire] || 0
|
25
|
+
if options.has_key?(:priority)
|
31
26
|
@priorities = options[:priority]
|
32
27
|
@default_priority = options[:default]
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
include ::Delayer
|
37
|
-
@expire = options[:expire] || 0
|
28
|
+
else
|
29
|
+
@priorities = [:normal]
|
30
|
+
@default_priority = :normal
|
38
31
|
end
|
39
32
|
end
|
40
33
|
end
|
@@ -43,19 +36,4 @@ module Delayer
|
|
43
36
|
(@default ||= generate_class).__send__(*args, &proc)
|
44
37
|
end
|
45
38
|
end
|
46
|
-
|
47
|
-
def initialize(*args)
|
48
|
-
super
|
49
|
-
@procedure = Procedure.new(self, &Proc.new)
|
50
|
-
end
|
51
|
-
|
52
|
-
# Cancel this job
|
53
|
-
# ==== Exception
|
54
|
-
# Delayer::AlreadyExecutedError :: if already called run()
|
55
|
-
# ==== Return
|
56
|
-
# self
|
57
|
-
def cancel
|
58
|
-
@procedure.cancel
|
59
|
-
self
|
60
|
-
end
|
61
39
|
end
|
data/lib/delayer/error.rb
CHANGED
@@ -7,6 +7,11 @@ module Delayer
|
|
7
7
|
class AlreadyCanceledError < TooLate; end
|
8
8
|
class AlreadyRunningError < TooLate; end
|
9
9
|
class InvalidPriorityError < Error; end
|
10
|
+
|
11
|
+
class RecursiveError < Error; end
|
12
|
+
class NoLowerLevelError < RecursiveError; end
|
13
|
+
class RemainJobsError < RecursiveError; end
|
14
|
+
|
10
15
|
def self.StateError(state)
|
11
16
|
case state
|
12
17
|
when :run
|
data/lib/delayer/extend.rb
CHANGED
@@ -1,19 +1,53 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
|
3
3
|
module Delayer
|
4
|
+
attr_reader :priority
|
5
|
+
|
6
|
+
Bucket = Struct.new(:first, :last, :priority_of, :stashed) do
|
7
|
+
def stash_size
|
8
|
+
if stashed
|
9
|
+
1 + stashed.stash_size
|
10
|
+
else
|
11
|
+
0
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.included(klass)
|
17
|
+
klass.class_eval do
|
18
|
+
extend Extend
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(priority = self.class.instance_eval{ @default_priority }, *args)
|
23
|
+
self.class.validate_priority priority
|
24
|
+
@priority = priority
|
25
|
+
@procedure = Procedure.new(self, &Proc.new)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Cancel this job
|
29
|
+
# ==== Exception
|
30
|
+
# Delayer::AlreadyExecutedError :: if already called run()
|
31
|
+
# ==== Return
|
32
|
+
# self
|
33
|
+
def cancel
|
34
|
+
@procedure.cancel
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
4
38
|
module Extend
|
5
39
|
attr_accessor :expire
|
6
40
|
attr_reader :exception
|
7
41
|
|
8
42
|
def self.extended(klass)
|
9
43
|
klass.class_eval do
|
10
|
-
@first_pointer = @last_pointer = nil
|
11
44
|
@busy = false
|
12
45
|
@expire = 0
|
13
46
|
@remain_hook = nil
|
14
47
|
@exception = nil
|
15
48
|
@remain_received = false
|
16
49
|
@lock = Mutex.new
|
50
|
+
@bucket = Bucket.new(nil, nil, {}, nil)
|
17
51
|
end
|
18
52
|
end
|
19
53
|
|
@@ -51,10 +85,10 @@ module Delayer
|
|
51
85
|
# ==== Return
|
52
86
|
# self
|
53
87
|
def run_once
|
54
|
-
if @
|
88
|
+
if @bucket.first
|
55
89
|
@busy = true
|
56
90
|
procedure = forward
|
57
|
-
procedure = forward while @
|
91
|
+
procedure = forward while @bucket.first and procedure.canceled?
|
58
92
|
procedure.run unless procedure.canceled?
|
59
93
|
end
|
60
94
|
ensure
|
@@ -74,13 +108,13 @@ module Delayer
|
|
74
108
|
# ==== Return
|
75
109
|
# true if no jobs has.
|
76
110
|
def empty?
|
77
|
-
!@
|
111
|
+
!@bucket.first
|
78
112
|
end
|
79
113
|
|
80
114
|
# Return remain jobs quantity.
|
81
115
|
# ==== Return
|
82
116
|
# Count of remain jobs
|
83
|
-
def size(node = @
|
117
|
+
def size(node = @bucket.first)
|
84
118
|
if node
|
85
119
|
1 + size(node.next)
|
86
120
|
else
|
@@ -94,11 +128,17 @@ module Delayer
|
|
94
128
|
# ==== Return
|
95
129
|
# self
|
96
130
|
def register(procedure)
|
131
|
+
priority = procedure.delayer.priority
|
97
132
|
lock.synchronize do
|
98
|
-
|
99
|
-
|
133
|
+
last_pointer = get_prev_point(priority)
|
134
|
+
if last_pointer
|
135
|
+
@bucket.priority_of[priority] = last_pointer.break procedure
|
100
136
|
else
|
101
|
-
|
137
|
+
procedure.next = @bucket.first
|
138
|
+
@bucket.priority_of[priority] = @bucket.first = procedure
|
139
|
+
end
|
140
|
+
if @bucket.last
|
141
|
+
@bucket.last = @bucket.priority_of[priority]
|
102
142
|
end
|
103
143
|
if @remain_hook and not @remain_received
|
104
144
|
@remain_received = true
|
@@ -112,13 +152,55 @@ module Delayer
|
|
112
152
|
@remain_hook = Proc.new
|
113
153
|
end
|
114
154
|
|
155
|
+
def get_prev_point(priority)
|
156
|
+
if @bucket.priority_of[priority]
|
157
|
+
@bucket.priority_of[priority]
|
158
|
+
else
|
159
|
+
next_index = @priorities.index(priority) - 1
|
160
|
+
get_prev_point @priorities[next_index] if 0 <= next_index
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def validate_priority(symbol)
|
165
|
+
unless @priorities.include? symbol
|
166
|
+
raise Delayer::InvalidPriorityError, "undefined priority '#{symbol}'"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# DelayerのStashレベルをインクリメントする。
|
171
|
+
# このメソッドが呼ばれたら、その時存在するジョブは退避され、stash_exit!が呼ばれるまで実行されない。
|
172
|
+
def stash_enter!
|
173
|
+
@bucket = Bucket.new(nil, nil, {}, @bucket)
|
174
|
+
self
|
175
|
+
end
|
176
|
+
|
177
|
+
# DelayerのStashレベルをデクリメントする。
|
178
|
+
# このメソッドを呼ぶ前に、現在のレベルに存在するすべてのジョブを実行し、Delayer#empty?がtrueを返すような状態になっている必要がある。
|
179
|
+
# ==== Raises
|
180
|
+
# [Delayer::NoLowerLevelError] stash_enter!が呼ばれていない時
|
181
|
+
# [Delayer::RemainJobsError] ジョブが残っているのにこのメソッドを呼んだ時
|
182
|
+
def stash_exit!
|
183
|
+
raise Delayer::NoLowerLevelError, 'stash_exit! called in level 0.' if !@bucket.stashed
|
184
|
+
raise Delayer::RemainJobsError, 'Current level has remain jobs. It must be empty current level jobs in call this method.' if !self.empty?
|
185
|
+
@bucket = @bucket.stashed
|
186
|
+
end
|
187
|
+
|
188
|
+
# 現在のDelayer Stashレベルを返す。
|
189
|
+
def stash_level
|
190
|
+
@bucket.stash_size
|
191
|
+
end
|
192
|
+
|
115
193
|
private
|
116
194
|
|
117
195
|
def forward
|
118
196
|
lock.synchronize do
|
119
|
-
prev = @
|
120
|
-
@
|
121
|
-
@
|
197
|
+
prev = @bucket.first
|
198
|
+
@bucket.first = @bucket.first.next
|
199
|
+
@bucket.last = nil unless @bucket.first
|
200
|
+
@bucket.priority_of.each do |priority, pointer|
|
201
|
+
@bucket.priority_of[priority] = @bucket.first if prev == pointer
|
202
|
+
end
|
203
|
+
prev.next = nil
|
122
204
|
prev
|
123
205
|
end
|
124
206
|
end
|
@@ -126,6 +208,5 @@ module Delayer
|
|
126
208
|
def lock
|
127
209
|
@lock
|
128
210
|
end
|
129
|
-
|
130
211
|
end
|
131
212
|
end
|
data/lib/delayer/version.rb
CHANGED
data/test/test_delayer.rb
CHANGED
@@ -270,7 +270,7 @@ class TestDelayer < Test::Unit::TestCase
|
|
270
270
|
end
|
271
271
|
end
|
272
272
|
delayer.run
|
273
|
-
threads.each
|
273
|
+
threads.each(&:join)
|
274
274
|
delayer.run
|
275
275
|
assert_equal(10000, buffer.size)
|
276
276
|
assert_equal((0..999).inject(&:+)*10, buffer.inject(&:+))
|
@@ -314,4 +314,43 @@ class TestDelayer < Test::Unit::TestCase
|
|
314
314
|
assert_equal([:remain, 0, 1, :remain, 2, 3, 4], a)
|
315
315
|
|
316
316
|
end
|
317
|
+
|
318
|
+
def test_recursive_mainloop
|
319
|
+
delayer = Delayer.generate_class
|
320
|
+
a = 0
|
321
|
+
delayer.new { a = 1 }
|
322
|
+
|
323
|
+
assert_equal(0, delayer.stash_level)
|
324
|
+
delayer.stash_enter!
|
325
|
+
assert_equal(1, delayer.stash_level)
|
326
|
+
|
327
|
+
delayer.new { a = 2 }
|
328
|
+
|
329
|
+
assert_equal(0, a)
|
330
|
+
delayer.run
|
331
|
+
assert_equal(2, a)
|
332
|
+
|
333
|
+
delayer.stash_exit!
|
334
|
+
assert_equal(0, delayer.stash_level)
|
335
|
+
|
336
|
+
delayer.run
|
337
|
+
assert_equal(1, a)
|
338
|
+
end
|
339
|
+
|
340
|
+
def test_pop_recursive_mainloop_remain_jobs
|
341
|
+
delayer = Delayer.generate_class
|
342
|
+
delayer.stash_enter!
|
343
|
+
delayer.new{ ; }
|
344
|
+
assert_raise Delayer::RemainJobsError do
|
345
|
+
delayer.stash_exit!
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def test_pop_recursive_mainloop_in_level_zero
|
350
|
+
delayer = Delayer.generate_class
|
351
|
+
assert_raise Delayer::NoLowerLevelError do
|
352
|
+
delayer.stash_exit!
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
317
356
|
end
|
data/test/test_priority.rb
CHANGED
metadata
CHANGED
@@ -1,38 +1,63 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: delayer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Toshiaki Asai
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2019-06-01 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: bundler
|
16
|
-
requirement:
|
17
|
-
none: false
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - ">="
|
20
18
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
19
|
+
version: '0'
|
22
20
|
type: :development
|
23
21
|
prerelease: false
|
24
|
-
version_requirements:
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
25
27
|
- !ruby/object:Gem::Dependency
|
26
28
|
name: rake
|
27
|
-
requirement:
|
28
|
-
none: false
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
29
30
|
requirements:
|
30
|
-
- -
|
31
|
+
- - ">="
|
31
32
|
- !ruby/object:Gem::Version
|
32
|
-
version:
|
33
|
+
version: 12.3.2
|
33
34
|
type: :development
|
34
35
|
prerelease: false
|
35
|
-
version_requirements:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 12.3.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: test-unit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.3.3
|
48
|
+
- - "<"
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '4.0'
|
51
|
+
type: :development
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 3.3.3
|
58
|
+
- - "<"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '4.0'
|
36
61
|
description: Delay the processing
|
37
62
|
email:
|
38
63
|
- toshi.alternative@gmail.com
|
@@ -40,7 +65,7 @@ executables: []
|
|
40
65
|
extensions: []
|
41
66
|
extra_rdoc_files: []
|
42
67
|
files:
|
43
|
-
- .gitignore
|
68
|
+
- ".gitignore"
|
44
69
|
- Gemfile
|
45
70
|
- LICENSE.txt
|
46
71
|
- README.md
|
@@ -49,35 +74,32 @@ files:
|
|
49
74
|
- lib/delayer.rb
|
50
75
|
- lib/delayer/error.rb
|
51
76
|
- lib/delayer/extend.rb
|
52
|
-
- lib/delayer/priority.rb
|
53
77
|
- lib/delayer/procedure.rb
|
54
78
|
- lib/delayer/version.rb
|
55
79
|
- test/test_delayer.rb
|
56
80
|
- test/test_priority.rb
|
57
|
-
homepage:
|
81
|
+
homepage: https://github.com/toshia/delayer
|
58
82
|
licenses:
|
59
83
|
- MIT
|
84
|
+
metadata: {}
|
60
85
|
post_install_message:
|
61
86
|
rdoc_options: []
|
62
87
|
require_paths:
|
63
88
|
- lib
|
64
89
|
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
90
|
requirements:
|
67
|
-
- -
|
91
|
+
- - ">="
|
68
92
|
- !ruby/object:Gem::Version
|
69
|
-
version:
|
93
|
+
version: 2.4.0
|
70
94
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
-
none: false
|
72
95
|
requirements:
|
73
|
-
- -
|
96
|
+
- - ">="
|
74
97
|
- !ruby/object:Gem::Version
|
75
98
|
version: '0'
|
76
99
|
requirements: []
|
77
|
-
|
78
|
-
rubygems_version: 1.8.11
|
100
|
+
rubygems_version: 3.0.3
|
79
101
|
signing_key:
|
80
|
-
specification_version:
|
102
|
+
specification_version: 4
|
81
103
|
summary: Delay the processing
|
82
104
|
test_files:
|
83
105
|
- test/test_delayer.rb
|
data/lib/delayer/priority.rb
DELETED
@@ -1,84 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
|
3
|
-
module Delayer
|
4
|
-
module Priority
|
5
|
-
attr_reader :priority
|
6
|
-
|
7
|
-
def self.included(klass)
|
8
|
-
klass.class_eval do
|
9
|
-
include ::Delayer
|
10
|
-
extend Extend
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def initialize(priority = self.class.instance_eval{ @default_priority }, *args)
|
15
|
-
self.class.validate_priority priority
|
16
|
-
@priority = priority
|
17
|
-
super(*args)
|
18
|
-
end
|
19
|
-
|
20
|
-
module Extend
|
21
|
-
def self.extended(klass)
|
22
|
-
klass.class_eval do
|
23
|
-
@priority_pointer = {}
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# register new job.
|
28
|
-
# ==== Args
|
29
|
-
# [procedure] job(Delayer::Procedure)
|
30
|
-
# ==== Return
|
31
|
-
# self
|
32
|
-
def register(procedure)
|
33
|
-
priority = procedure.delayer.priority
|
34
|
-
lock.synchronize do
|
35
|
-
last_pointer = get_prev_point(priority)
|
36
|
-
if last_pointer
|
37
|
-
@priority_pointer[priority] = last_pointer.break procedure
|
38
|
-
else
|
39
|
-
procedure.next = @first_pointer
|
40
|
-
@priority_pointer[priority] = @first_pointer = procedure
|
41
|
-
end
|
42
|
-
if @last_pointer
|
43
|
-
@last_pointer = @priority_pointer[priority]
|
44
|
-
end
|
45
|
-
if @remain_hook and not @remain_received
|
46
|
-
@remain_received = true
|
47
|
-
@remain_hook.call
|
48
|
-
end
|
49
|
-
end
|
50
|
-
self
|
51
|
-
end
|
52
|
-
|
53
|
-
def get_prev_point(priority)
|
54
|
-
if @priority_pointer[priority]
|
55
|
-
@priority_pointer[priority]
|
56
|
-
else
|
57
|
-
next_index = @priorities.index(priority) - 1
|
58
|
-
get_prev_point @priorities[next_index] if 0 <= next_index
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def validate_priority(symbol)
|
63
|
-
unless @priorities.include? symbol
|
64
|
-
raise Delayer::InvalidPriorityError, "undefined priority '#{symbol}'"
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
private
|
69
|
-
|
70
|
-
def forward
|
71
|
-
lock.synchronize do
|
72
|
-
prev = @first_pointer
|
73
|
-
@first_pointer = @first_pointer.next
|
74
|
-
@last_pointer = nil unless @first_pointer
|
75
|
-
@priority_pointer.each do |priority, pointer|
|
76
|
-
@priority_pointer[priority] = @first_pointer if prev == pointer
|
77
|
-
end
|
78
|
-
prev
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|