delayer 0.0.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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