timemachine 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: 2faa5eb5940725efe1ad79b7bbdc332744005dc51b82135428732ab29e173627
4
+ data.tar.gz: 781d8866427bf3eae6e55f444818d380618376c75b0b7da7b069d78b0a4e1eff
5
+ SHA512:
6
+ metadata.gz: c04709eb092377f4fc184a1522e5a51bbacba3103008dcfee03e9841e7c8bb2055317994560d57cfcd085a52cb0e7f0ec91b422f3679838376dfc1bde56365d5
7
+ data.tar.gz: 0b321773be1198e5def0316f35cc220b985f9d2bd88a5e8b9bb92c8a6071dcbbcc1c3d7eb0e7590acf4b90454b91a5447ee192455e057b74e8ca849418fba59b
data/lib/executor.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TimeMachine
4
+ module Executors
5
+ class Executor
6
+ def execute(&block)
7
+ raise NotImplementedError
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'executor'
4
+
5
+ module TimeMachine
6
+ module Executors
7
+ class ThreadExecutor < Executor
8
+ def execute(&block)
9
+ Thread.new(&block)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread_executor'
4
+
5
+ module TimeMachine
6
+ TimedTask = Struct.new(:handle, :timeup, :block)
7
+ TaskResult = Struct.new(:handle, :status, :record_result, :result)
8
+
9
+ class DuplicateHandleError < RuntimeError; end
10
+
11
+ class TimeMachine
12
+ SCHEDULER_DEFAULT_TIMEOUT = 3
13
+ RANDOM_HANDLE_LENGTH = 16
14
+ private_constant :SCHEDULER_DEFAULT_TIMEOUT
15
+
16
+ public
17
+ def initialize(executor= Executors::ThreadExecutor.new, more_timely=false)
18
+ @running = false
19
+ @timeout_queue = []
20
+
21
+ @result_queue = {}
22
+ @pending_schedule_queue = []
23
+ @cancel_queue = []
24
+
25
+ @executor = executor
26
+ @scheduler_thread = nil
27
+ @scheduler_mut = if more_timely then Mutex.new else nil end
28
+ @user_mut = Mutex.new
29
+ end
30
+
31
+ def at(timeup, name: nil, record_result: false, &block)
32
+ handle = name || _generate_handle
33
+
34
+ @user_mut.synchronize do
35
+ raise DuplicateHandleError if @result_queue.key? handle
36
+ result_slot = TaskResult.new(handle, :UNSCHEDULED, record_result, nil)
37
+ @result_queue[handle] = result_slot
38
+ @pending_schedule_queue << TimedTask.new(
39
+ handle, timeup, block
40
+ )
41
+ end
42
+
43
+ _bg_wakeup
44
+ handle
45
+ end
46
+
47
+ def after(timeout, name: nil, record_result: false, &block)
48
+ at(Time.now + timeout, name: name, record_result: record_result, &block)
49
+ end
50
+
51
+ def get_result(handle)
52
+ @user_mut.synchronize do
53
+ @result_queue[handle]
54
+ end
55
+ end
56
+
57
+ def pop_result(handle)
58
+ @user_mut.synchronize do
59
+ res = @result_queue[handle]
60
+ if res&.status==:FINISHED || res&.status==:CANCELLED
61
+ @result_queue.delete(handle)
62
+ else
63
+ res
64
+ end
65
+ end
66
+ end
67
+
68
+ def cancel(handle)
69
+ @user_mut.synchronize do
70
+ @cancel_queue << handle
71
+ end
72
+ _bg_wakeup
73
+ end
74
+
75
+ def start()
76
+ @user_mut.synchronize do
77
+ return if @running
78
+ @running = true
79
+ @scheduler_thread = Thread.new { _bg_run() }
80
+ end
81
+ end
82
+
83
+ def stop()
84
+ @user_mut.synchronize do
85
+ return unless @running
86
+ @running = false
87
+ @result_queue.each_key { |h| @cancel_queue << h}
88
+ end
89
+ _bg_wakeup
90
+ end
91
+
92
+ private
93
+ def _generate_handle
94
+ h = (Time.now.to_f * 1000).to_i.to_s(36)
95
+ padding_len = RANDOM_HANDLE_LENGTH - h.size
96
+ randstr = Random.rand(('z'*padding_len).to_i(36))
97
+ .to_s(36).rjust(padding_len, '0')
98
+ h + randstr
99
+ end
100
+
101
+ def _bg_wakeup
102
+ @scheduler_mut&.lock
103
+ @scheduler_thread.run
104
+ @scheduler_mut&.unlock
105
+ end
106
+
107
+ def _bg_run
108
+ @scheduler_mut&.lock
109
+ while @running
110
+ @user_mut.synchronize do
111
+ @cancel_queue.each { |h| _bg_cancelTask(h) }
112
+ @cancel_queue.clear
113
+ @pending_schedule_queue.each { |t| _bg_enqueueTask(t) }
114
+ @pending_schedule_queue.clear
115
+ end
116
+
117
+ abs_t = Time.now
118
+ ind = @timeout_queue.index { |t| t.timeup > abs_t } || @timeout_queue.size
119
+
120
+ @timeout_queue[0...ind].each do |t|
121
+ break if !@running
122
+ _bg_dispatchTask(t)
123
+ end
124
+
125
+ @timeout_queue = @timeout_queue[ind..]
126
+
127
+ timeout = (
128
+ @timeout_queue[0]&.timeup ||
129
+ (abs_t + SCHEDULER_DEFAULT_TIMEOUT)
130
+ ) - abs_t
131
+ timeout = 0 if timeout < 0
132
+
133
+ @scheduler_mut&.unlock
134
+ sleep timeout
135
+ @scheduler_mut&.lock
136
+ end
137
+ @scheduler_mut&.unlock
138
+ end
139
+
140
+ def _bg_enqueueTask(task)
141
+ return if @result_queue[task.handle].status != :UNSCHEDULED
142
+
143
+ @result_queue[task.handle].status = :PENDING
144
+ @timeout_queue.insert(@timeout_queue.index do |t|
145
+ t.timeup > task.timeup
146
+ end || -1, task)
147
+ end
148
+
149
+ def _bg_cancelTask(handle)
150
+ result_slot = @result_queue[handle]
151
+ case result_slot&.status
152
+ when :UNSCHEDULED
153
+ result_slot.status = :CANCELLED
154
+ when :PENDING
155
+ result_slot.status = :CANCELLED
156
+ @timeout_queue.delete_if do |task|
157
+ task.handle == handle
158
+ end
159
+ end
160
+
161
+ @result_queue.delete(handle) unless result_slot.record_result
162
+ end
163
+
164
+ def _bg_dispatchTask(task)
165
+ return if !@running
166
+ result_slot = @result_queue[task.handle]
167
+ return if result_slot&.status != :PENDING
168
+
169
+ result_slot.status = :RUNNING
170
+ @executor.execute do
171
+ result_slot&.result = task.block.call
172
+ @user_mut.synchronize {
173
+ result_slot.record_result ? (result_slot&.status = :FINISHED) : @result_queue.delete(task.handle)
174
+ }
175
+ end
176
+ end
177
+ end
178
+
179
+ end
metadata ADDED
@@ -0,0 +1,42 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: timemachine
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - LTW
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-09-21 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: A library that executes task after timeup or timeout.
13
+ email: ltwsamuel@outlook.com
14
+ executables: []
15
+ extensions: []
16
+ extra_rdoc_files: []
17
+ files:
18
+ - lib/executor.rb
19
+ - lib/thread_executor.rb
20
+ - lib/timemachine.rb
21
+ homepage: https://github.com/LasSino/timemachine
22
+ licenses:
23
+ - MIT
24
+ metadata: {}
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubygems_version: 3.6.2
40
+ specification_version: 4
41
+ summary: A library that executes task after timeup or timeout.
42
+ test_files: []