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 +7 -0
- data/lib/executor.rb +11 -0
- data/lib/thread_executor.rb +13 -0
- data/lib/timemachine.rb +179 -0
- metadata +42 -0
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
data/lib/timemachine.rb
ADDED
|
@@ -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: []
|