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 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
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ /vendor/
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", "~> 1.2.3"
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
- if options[:priority]
28
- Class.new do
29
- include Priority
30
- @expire = options[:expire] || 0
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
- end
34
- else
35
- Class.new do
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
@@ -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 @first_pointer
88
+ if @bucket.first
55
89
  @busy = true
56
90
  procedure = forward
57
- procedure = forward while @first_pointer and procedure.canceled?
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
- !@first_pointer
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 = @first_pointer)
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
- if @last_pointer
99
- @last_pointer = @last_pointer.break procedure
133
+ last_pointer = get_prev_point(priority)
134
+ if last_pointer
135
+ @bucket.priority_of[priority] = last_pointer.break procedure
100
136
  else
101
- @last_pointer = @first_pointer = procedure
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 = @first_pointer
120
- @first_pointer = @first_pointer.next
121
- @last_pointer = nil unless @first_pointer
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
@@ -1,3 +1,3 @@
1
1
  module Delayer
2
- VERSION = "0.0.2"
2
+ VERSION = "1.0.0"
3
3
  end
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 &:join
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
@@ -214,7 +214,7 @@ class TestPriorityDelayer < Test::Unit::TestCase
214
214
  end
215
215
  end
216
216
  delayer.run
217
- threads.each &:join
217
+ threads.each(&:join)
218
218
  delayer.run
219
219
  assert_equal(10000, buffer.size)
220
220
  assert_equal((0..999).inject(&:+)*10, buffer.inject(&:+))
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.2
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: 2013-06-12 00:00:00.000000000 Z
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: &8375180 !ruby/object:Gem::Requirement
17
- none: false
15
+ requirement: !ruby/object:Gem::Requirement
18
16
  requirements:
19
- - - ~>
17
+ - - ">="
20
18
  - !ruby/object:Gem::Version
21
- version: 1.2.3
19
+ version: '0'
22
20
  type: :development
23
21
  prerelease: false
24
- version_requirements: *8375180
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: &8374760 !ruby/object:Gem::Requirement
28
- none: false
29
+ requirement: !ruby/object:Gem::Requirement
29
30
  requirements:
30
- - - ! '>='
31
+ - - ">="
31
32
  - !ruby/object:Gem::Version
32
- version: '0'
33
+ version: 12.3.2
33
34
  type: :development
34
35
  prerelease: false
35
- version_requirements: *8374760
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: '0'
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
- rubyforge_project:
78
- rubygems_version: 1.8.11
100
+ rubygems_version: 3.0.3
79
101
  signing_key:
80
- specification_version: 3
102
+ specification_version: 4
81
103
  summary: Delay the processing
82
104
  test_files:
83
105
  - test/test_delayer.rb
@@ -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