pindo 5.13.12 → 5.13.13
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 +4 -4
- data/lib/pindo/base/funlog.rb +62 -5
- data/lib/pindo/base/git_handler.rb +83 -22
- data/lib/pindo/base/output_sink.rb +69 -0
- data/lib/pindo/command/android/autobuild.rb +57 -8
- data/lib/pindo/command/appstore/autobuild.rb +10 -1
- data/lib/pindo/command/ios/autobuild.rb +59 -7
- data/lib/pindo/command/jps/media.rb +164 -13
- data/lib/pindo/command/jps/upload.rb +14 -9
- data/lib/pindo/command/unity/autobuild.rb +64 -10
- data/lib/pindo/command/utils/tag.rb +9 -1
- data/lib/pindo/command/web/autobuild.rb +59 -10
- data/lib/pindo/module/android/android_build_helper.rb +6 -7
- data/lib/pindo/module/build/git_repo_helper.rb +29 -25
- data/lib/pindo/module/pgyer/pgyerhelper.rb +174 -77
- data/lib/pindo/module/task/core/concurrent_execution_strategy.rb +237 -0
- data/lib/pindo/module/task/core/dependency_checker.rb +123 -0
- data/lib/pindo/module/task/core/execution_strategy.rb +61 -0
- data/lib/pindo/module/task/core/resource_lock_manager.rb +190 -0
- data/lib/pindo/module/task/core/serial_execution_strategy.rb +60 -0
- data/lib/pindo/module/task/core/task_executor.rb +131 -0
- data/lib/pindo/module/task/core/task_queue.rb +221 -0
- data/lib/pindo/module/task/model/build/android_build_dev_task.rb +1 -1
- data/lib/pindo/module/task/model/build/android_build_task.rb +6 -2
- data/lib/pindo/module/task/model/build/ios_build_dev_task.rb +2 -3
- data/lib/pindo/module/task/model/build/ios_build_task.rb +6 -0
- data/lib/pindo/module/task/model/build_task.rb +22 -0
- data/lib/pindo/module/task/model/git/git_commit_task.rb +11 -2
- data/lib/pindo/module/task/model/git_task.rb +6 -0
- data/lib/pindo/module/task/model/jps/jps_message_task.rb +9 -11
- data/lib/pindo/module/task/model/jps/jps_upload_media_task.rb +204 -103
- data/lib/pindo/module/task/model/jps_task.rb +0 -1
- data/lib/pindo/module/task/model/unity_task.rb +38 -2
- data/lib/pindo/module/task/output/multi_line_output_manager.rb +380 -0
- data/lib/pindo/module/task/output/multi_line_task_display.rb +185 -0
- data/lib/pindo/module/task/output/stdout_redirector.rb +95 -0
- data/lib/pindo/module/task/pindo_task.rb +133 -9
- data/lib/pindo/module/task/task_manager.rb +98 -268
- data/lib/pindo/module/task/task_reporter.rb +135 -0
- data/lib/pindo/module/task/task_resources/resource_instance.rb +90 -0
- data/lib/pindo/module/task/task_resources/resource_registry.rb +105 -0
- data/lib/pindo/module/task/task_resources/resource_type.rb +59 -0
- data/lib/pindo/module/task/task_resources/types/directory_based_resource.rb +63 -0
- data/lib/pindo/module/task/task_resources/types/global_exclusive_resource.rb +33 -0
- data/lib/pindo/module/task/task_resources/types/global_shared_resource.rb +34 -0
- data/lib/pindo/module/xcode/xcode_build_helper.rb +26 -8
- data/lib/pindo/options/groups/jps_options.rb +10 -0
- data/lib/pindo/options/groups/task_options.rb +39 -0
- data/lib/pindo/version.rb +3 -2
- metadata +20 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
require 'pindo/base/funlog'
|
|
2
|
+
require 'thread'
|
|
3
|
+
require_relative 'execution_strategy'
|
|
4
|
+
|
|
5
|
+
module Pindo
|
|
6
|
+
module TaskSystem
|
|
7
|
+
# ConcurrentExecutionStrategy - 并发执行策略
|
|
8
|
+
#
|
|
9
|
+
# 职责:
|
|
10
|
+
# - 在多线程中并发执行独立任务
|
|
11
|
+
# - 控制最大工作线程数
|
|
12
|
+
# - 处理线程安全和任务同步
|
|
13
|
+
class ConcurrentExecutionStrategy < ExecutionStrategy
|
|
14
|
+
# 初始化
|
|
15
|
+
# @param max_workers [Integer] 最大工作线程数
|
|
16
|
+
def initialize(max_workers: 4)
|
|
17
|
+
@max_workers = max_workers
|
|
18
|
+
@mutex = Mutex.new
|
|
19
|
+
@running_tasks = [] # 正在运行的任务
|
|
20
|
+
@threads = [] # 工作线程
|
|
21
|
+
@threads = [] # 工作线程
|
|
22
|
+
@completion_queue = Queue.new # 任务完成通知队列
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# 饥饿阈值:任务被跳过多少次后进入饥饿模式
|
|
26
|
+
STARVATION_THRESHOLD = 5
|
|
27
|
+
|
|
28
|
+
# 执行任务
|
|
29
|
+
# @param task_manager [TaskManager] 任务管理器
|
|
30
|
+
def execute(task_manager)
|
|
31
|
+
queue = task_manager.queue
|
|
32
|
+
checker = task_manager.dependency_checker
|
|
33
|
+
executor = task_manager.executor
|
|
34
|
+
|
|
35
|
+
loop do
|
|
36
|
+
# 启动新任务(直到达到 max_workers 上限)
|
|
37
|
+
start_pending_tasks(queue, checker, executor, task_manager)
|
|
38
|
+
|
|
39
|
+
# 退出条件:无正在运行的任务 + 待执行队列为空
|
|
40
|
+
if no_running_tasks? && queue.pending_empty?
|
|
41
|
+
Funlog.info("[并发策略] 退出循环:无运行任务且队列为空")
|
|
42
|
+
break
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# 如果所有任务都因依赖问题阻塞,且没有正在运行的任务
|
|
46
|
+
if no_running_tasks? && checker.all_pending_blocked?
|
|
47
|
+
pending_count = queue.pending_snapshot.size
|
|
48
|
+
Funlog.warning("[并发策略] 所有剩余任务都因依赖问题无法执行 (待执行: #{pending_count})")
|
|
49
|
+
# 打印所有待执行任务的状态
|
|
50
|
+
queue.pending_snapshot.each do |task|
|
|
51
|
+
dep_status = checker.check_dependencies(task)
|
|
52
|
+
Funlog.warning(" - 任务: #{task.name}, 依赖检查结果: #{dep_status}")
|
|
53
|
+
end
|
|
54
|
+
break
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# 等待至少一个任务完成
|
|
58
|
+
wait_for_task_completion if has_running_tasks?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# 等待所有线程完成
|
|
62
|
+
@threads.each(&:join)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# 策略名称
|
|
66
|
+
# @return [String]
|
|
67
|
+
def name
|
|
68
|
+
"并发执行 (#{@max_workers} 线程)"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
# 启动待执行任务
|
|
74
|
+
# @param queue [TaskQueue] 任务队列
|
|
75
|
+
# @param checker [DependencyChecker] 依赖检查器
|
|
76
|
+
# @param executor [TaskExecutor] 任务执行器
|
|
77
|
+
# @param task_manager [TaskManager] 任务管理器
|
|
78
|
+
# 启动待执行任务
|
|
79
|
+
# @param queue [TaskQueue] 任务队列
|
|
80
|
+
# @param checker [DependencyChecker] 依赖检查器
|
|
81
|
+
# @param executor [TaskExecutor] 任务执行器
|
|
82
|
+
# @param task_manager [TaskManager] 任务管理器
|
|
83
|
+
def start_pending_tasks(queue, checker, executor, task_manager)
|
|
84
|
+
resource_manager = task_manager.resource_lock_manager
|
|
85
|
+
|
|
86
|
+
while can_start_more_tasks?
|
|
87
|
+
# 先检查依赖(不在锁内)
|
|
88
|
+
pending_tasks = queue.pending_snapshot
|
|
89
|
+
if ENV['PINDO_DEBUG']
|
|
90
|
+
Funlog.info("[并发策略] 待执行任务数: #{pending_tasks.size}, 正在运行: #{@running_tasks.size}/#{@max_workers}")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
executable_tasks = pending_tasks.select { |t| checker.dependencies_satisfied?(t) }
|
|
94
|
+
if ENV['PINDO_DEBUG']
|
|
95
|
+
Funlog.info("[并发策略] 依赖满足的任务数: #{executable_tasks.size}")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if executable_tasks.empty?
|
|
99
|
+
if ENV['PINDO_DEBUG']
|
|
100
|
+
Funlog.info("[并发策略] 没有可执行的任务,退出启动循环")
|
|
101
|
+
end
|
|
102
|
+
break
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# 尝试获取第一个可用的任务(原子操作)
|
|
106
|
+
task = nil
|
|
107
|
+
starvation_mode = false
|
|
108
|
+
|
|
109
|
+
executable_tasks.each do |candidate|
|
|
110
|
+
# 调试:打印任务资源信息
|
|
111
|
+
if ENV['PINDO_DEBUG']
|
|
112
|
+
resources = candidate.required_resources
|
|
113
|
+
Funlog.info("[并发策略] 检查任务 #{candidate.name} 的资源: #{resources.inspect}")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# 检查资源是否可用
|
|
117
|
+
if resource_manager.available?(candidate.required_resources)
|
|
118
|
+
if ENV['PINDO_DEBUG']
|
|
119
|
+
Funlog.info("[并发策略] 资源可用,尝试获取任务: #{candidate.name}")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# 尝试获取任务和资源
|
|
123
|
+
task = queue.find_and_remove do |t|
|
|
124
|
+
t.id == candidate.id &&
|
|
125
|
+
resource_manager.try_acquire(t.required_resources, t.id)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if task
|
|
129
|
+
if ENV['PINDO_DEBUG']
|
|
130
|
+
Funlog.info("[并发策略] ✓ 成功获取任务: #{task.name}")
|
|
131
|
+
Funlog.info("[并发策略] 当前锁定资源: #{resource_manager.lock_status.inspect}")
|
|
132
|
+
end
|
|
133
|
+
# 成功获取资源,重置跳过计数
|
|
134
|
+
task.skip_count = 0
|
|
135
|
+
break
|
|
136
|
+
end
|
|
137
|
+
else
|
|
138
|
+
# 资源不可用,增加跳过计数
|
|
139
|
+
candidate.skip_count += 1
|
|
140
|
+
if ENV['PINDO_DEBUG']
|
|
141
|
+
Funlog.info("[并发策略] 任务 #{candidate.name} 资源不足 (跳过次数: #{candidate.skip_count})")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# 检查是否达到饥饿阈值
|
|
145
|
+
if candidate.skip_count >= STARVATION_THRESHOLD
|
|
146
|
+
Funlog.warning("[并发策略] 任务 #{candidate.name} 进入饥饿模式 (跳过 #{candidate.skip_count} 次)")
|
|
147
|
+
Funlog.warning(" -> 暂停调度新任务,直到该任务能够执行")
|
|
148
|
+
starvation_mode = true
|
|
149
|
+
break # 立即停止调度其他任务
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# 如果进入饥饿模式,直接退出循环,不再调度任何新任务
|
|
155
|
+
if starvation_mode
|
|
156
|
+
break
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
unless task
|
|
160
|
+
if ENV['PINDO_DEBUG']
|
|
161
|
+
Funlog.warning("[并发策略] 无法获取任务(可能资源被占用)")
|
|
162
|
+
end
|
|
163
|
+
break
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
start_task_in_thread(task, executor, task_manager)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# 在线程中启动任务
|
|
171
|
+
# @param task [PindoTask] 任务对象
|
|
172
|
+
# @param executor [TaskExecutor] 任务执行器
|
|
173
|
+
# @param task_manager [TaskManager] 任务管理器
|
|
174
|
+
def start_task_in_thread(task, executor, task_manager)
|
|
175
|
+
# 将任务添加到队列的运行队列(用于依赖检查)
|
|
176
|
+
task_manager.queue.add_running(task)
|
|
177
|
+
|
|
178
|
+
thread = Thread.new do
|
|
179
|
+
begin
|
|
180
|
+
executor.execute_task_sync(task, task_manager)
|
|
181
|
+
if ENV['PINDO_DEBUG']
|
|
182
|
+
Funlog.info("[并发策略] 任务完成: #{task.name}, 状态: #{task.status}")
|
|
183
|
+
end
|
|
184
|
+
ensure
|
|
185
|
+
cleanup_task(task, task_manager)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
@mutex.synchronize do
|
|
190
|
+
@running_tasks << task
|
|
191
|
+
@threads << thread
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# 清理任务
|
|
196
|
+
# @param task [PindoTask] 任务对象
|
|
197
|
+
# @param task_manager [TaskManager] 任务管理器
|
|
198
|
+
def cleanup_task(task, task_manager)
|
|
199
|
+
# 从队列的运行队列中移除
|
|
200
|
+
task_manager.queue.remove_running(task)
|
|
201
|
+
|
|
202
|
+
@mutex.synchronize do
|
|
203
|
+
@running_tasks.delete(task)
|
|
204
|
+
@threads.delete(Thread.current)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# 通知主循环任务已完成(非阻塞)
|
|
208
|
+
@completion_queue.push(task)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# 是否可以启动更多任务
|
|
212
|
+
# @return [Boolean]
|
|
213
|
+
def can_start_more_tasks?
|
|
214
|
+
@mutex.synchronize { @running_tasks.size < @max_workers }
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# 是否有正在运行的任务
|
|
218
|
+
# @return [Boolean]
|
|
219
|
+
def has_running_tasks?
|
|
220
|
+
@mutex.synchronize { !@running_tasks.empty? }
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# 是否没有正在运行的任务
|
|
224
|
+
# @return [Boolean]
|
|
225
|
+
def no_running_tasks?
|
|
226
|
+
@mutex.synchronize { @running_tasks.empty? }
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# 等待任意一个任务完成
|
|
230
|
+
# 使用 Queue.pop 阻塞等待,而不是等待特定线程
|
|
231
|
+
# 这样即使第一个任务很慢,后续快速任务完成后也能立即启动新任务
|
|
232
|
+
def wait_for_task_completion
|
|
233
|
+
@completion_queue.pop # 阻塞等待任意一个任务完成
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
require 'pindo/base/funlog'
|
|
2
|
+
|
|
3
|
+
module Pindo
|
|
4
|
+
module TaskSystem
|
|
5
|
+
# DependencyChecker - 依赖检查器
|
|
6
|
+
#
|
|
7
|
+
# 职责:
|
|
8
|
+
# - 检查任务是否可以执行
|
|
9
|
+
# - 检查依赖状态
|
|
10
|
+
# - 检查资源可用性
|
|
11
|
+
# - 标记任务失败或取消
|
|
12
|
+
class DependencyChecker
|
|
13
|
+
def initialize(queue, resource_lock_manager)
|
|
14
|
+
@queue = queue
|
|
15
|
+
@resource_lock_manager = resource_lock_manager
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# 判断任务依赖是否满足(不检查资源)
|
|
19
|
+
# @param task [PindoTask] 任务对象
|
|
20
|
+
# @return [Boolean]
|
|
21
|
+
def dependencies_satisfied?(task)
|
|
22
|
+
# 检查依赖状态
|
|
23
|
+
dependency_check = check_dependencies(task)
|
|
24
|
+
|
|
25
|
+
case dependency_check
|
|
26
|
+
when :failed
|
|
27
|
+
mark_task_failed(task, "依赖任务失败")
|
|
28
|
+
false
|
|
29
|
+
when :cancelled
|
|
30
|
+
mark_task_cancelled(task, "依赖任务被取消")
|
|
31
|
+
false
|
|
32
|
+
when :waiting
|
|
33
|
+
false # 依赖未完成,继续等待
|
|
34
|
+
else
|
|
35
|
+
true # 依赖已满足
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# 判断任务是否可以执行(检查依赖和资源)
|
|
40
|
+
# @param task [PindoTask] 任务对象
|
|
41
|
+
# @return [Boolean]
|
|
42
|
+
def can_execute?(task)
|
|
43
|
+
dependencies_satisfied?(task) && check_resources_available?(task)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# 检查资源是否可用
|
|
47
|
+
# @param task [PindoTask] 任务对象
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
def check_resources_available?(task)
|
|
50
|
+
resource_names = task.required_resources
|
|
51
|
+
return true if resource_names.nil? || resource_names.empty?
|
|
52
|
+
|
|
53
|
+
@resource_lock_manager.available?(resource_names)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# 检查依赖状态
|
|
57
|
+
# @param task [PindoTask] 任务对象
|
|
58
|
+
# @return [Symbol] :ready, :waiting, :failed, :cancelled
|
|
59
|
+
def check_dependencies(task)
|
|
60
|
+
return :ready if task.dependencies.empty?
|
|
61
|
+
|
|
62
|
+
task.dependencies.each do |dep_id|
|
|
63
|
+
dep_task = @queue.find(dep_id)
|
|
64
|
+
|
|
65
|
+
# 依赖任务不存在
|
|
66
|
+
unless dep_task
|
|
67
|
+
Funlog.error("[依赖检查] 任务 #{task.name}: 依赖任务 #{dep_id} 不存在")
|
|
68
|
+
return :failed
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# 检查依赖任务状态
|
|
72
|
+
case dep_task.status
|
|
73
|
+
when TaskStatus::SUCCESS
|
|
74
|
+
next # 这个依赖已完成
|
|
75
|
+
when TaskStatus::FAILED
|
|
76
|
+
Funlog.error("[依赖检查] 任务 #{task.name}: 依赖任务 #{dep_task.name} 失败 (状态: #{dep_task.status})")
|
|
77
|
+
return :failed # 依赖失败
|
|
78
|
+
when TaskStatus::CANCELLED
|
|
79
|
+
Funlog.warning("[依赖检查] 任务 #{task.name}: 依赖任务 #{dep_task.name} 被取消 (状态: #{dep_task.status})")
|
|
80
|
+
return :cancelled # 依赖被取消
|
|
81
|
+
else
|
|
82
|
+
# Funlog.info("[依赖检查] 任务 #{task.name}: 依赖任务 #{dep_task.name} 未完成 (状态: #{dep_task.status})")
|
|
83
|
+
return :waiting # 依赖未完成
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
:ready # 所有依赖都成功完成
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# 检查是否所有待执行任务都因依赖问题无法执行
|
|
91
|
+
# @return [Boolean]
|
|
92
|
+
def all_pending_blocked?
|
|
93
|
+
@queue.all_pending? { |t| check_dependencies(t) != :ready }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
# 标记任务为失败
|
|
99
|
+
# @param task [PindoTask] 任务对象
|
|
100
|
+
# @param reason [String] 失败原因
|
|
101
|
+
def mark_task_failed(task, reason)
|
|
102
|
+
@queue.remove_pending(task)
|
|
103
|
+
task.status = TaskStatus::FAILED
|
|
104
|
+
task.error = RuntimeError.new(reason)
|
|
105
|
+
@queue.mark_completed(task)
|
|
106
|
+
|
|
107
|
+
Funlog.error("任务 #{task.name} 因#{reason}而被标记为失败")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# 标记任务为取消
|
|
111
|
+
# @param task [PindoTask] 任务对象
|
|
112
|
+
# @param reason [String] 取消原因
|
|
113
|
+
def mark_task_cancelled(task, reason)
|
|
114
|
+
@queue.remove_pending(task)
|
|
115
|
+
task.status = TaskStatus::CANCELLED
|
|
116
|
+
task.error = RuntimeError.new(reason)
|
|
117
|
+
@queue.mark_completed(task)
|
|
118
|
+
|
|
119
|
+
Funlog.warning("任务 #{task.name} 因#{reason}而被取消")
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Pindo
|
|
2
|
+
module TaskSystem
|
|
3
|
+
# ExecutionStrategy - 执行策略基类
|
|
4
|
+
#
|
|
5
|
+
# 定义所有执行策略必须实现的接口
|
|
6
|
+
# 遵循策略模式(Strategy Pattern)
|
|
7
|
+
#
|
|
8
|
+
# 职责:
|
|
9
|
+
# - 定义统一的策略接口
|
|
10
|
+
# - 强制子类实现核心方法
|
|
11
|
+
# - 提供策略工厂方法
|
|
12
|
+
# - 支持里氏替换原则(LSP)
|
|
13
|
+
class ExecutionStrategy
|
|
14
|
+
# 执行任务(抽象方法,子类必须实现)
|
|
15
|
+
# @param task_manager [TaskManager] 任务管理器
|
|
16
|
+
# @raise [NotImplementedError] 如果子类未实现此方法
|
|
17
|
+
def execute(task_manager)
|
|
18
|
+
raise NotImplementedError, "#{self.class} must implement #execute"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# 策略名称(抽象方法,子类必须实现)
|
|
22
|
+
# @return [String] 策略的显示名称
|
|
23
|
+
# @raise [NotImplementedError] 如果子类未实现此方法
|
|
24
|
+
def name
|
|
25
|
+
raise NotImplementedError, "#{self.class} must implement #name"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# ==================== 策略工厂(类方法)====================
|
|
29
|
+
|
|
30
|
+
# 创建执行策略
|
|
31
|
+
# @param mode [Symbol] 执行模式 (:serial, :concurrent)
|
|
32
|
+
# @param options [Hash] 选项
|
|
33
|
+
# @option options [Integer] :max_workers 最大工作线程数(并发模式)
|
|
34
|
+
# @return [ExecutionStrategy] 策略对象
|
|
35
|
+
def self.create(mode, options = {})
|
|
36
|
+
case mode
|
|
37
|
+
when :serial
|
|
38
|
+
SerialExecutionStrategy.new
|
|
39
|
+
when :concurrent
|
|
40
|
+
max_workers = options[:max_workers] || detect_optimal_workers
|
|
41
|
+
ConcurrentExecutionStrategy.new(max_workers: max_workers)
|
|
42
|
+
else
|
|
43
|
+
raise ArgumentError, "Unknown execution mode: #{mode}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# 检测最优工作线程数
|
|
48
|
+
# @return [Integer] 工作线程数
|
|
49
|
+
def self.detect_optimal_workers
|
|
50
|
+
require 'etc'
|
|
51
|
+
[Etc.nprocessors, 2].max
|
|
52
|
+
rescue
|
|
53
|
+
4
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# 加载具体的执行策略实现
|
|
60
|
+
require_relative 'serial_execution_strategy'
|
|
61
|
+
require_relative 'concurrent_execution_strategy'
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
require_relative '../task_resources/resource_registry'
|
|
2
|
+
require_relative '../task_resources/resource_instance'
|
|
3
|
+
|
|
4
|
+
module Pindo
|
|
5
|
+
module TaskSystem
|
|
6
|
+
# 资源锁管理器 v3.0 - 支持基于上下文的资源锁定
|
|
7
|
+
#
|
|
8
|
+
# 功能:
|
|
9
|
+
# 1. 支持基于目录的资源(相同目录互斥,不同目录可并行)
|
|
10
|
+
# 2. 支持全局互斥资源(永远互斥)
|
|
11
|
+
# 3. 支持全局共享资源(永不冲突)
|
|
12
|
+
# 4. 原子性获取 - 要么全部获取,要么全部失败
|
|
13
|
+
# 5. 线程安全 - 使用 Mutex 保护
|
|
14
|
+
# 6. 支持阻塞式获取(带超时)
|
|
15
|
+
# 7. 支持部分释放资源
|
|
16
|
+
#
|
|
17
|
+
# 资源规格格式:
|
|
18
|
+
# { type: :xcode, directory: "/path/to/project" }
|
|
19
|
+
# { type: :keychain }
|
|
20
|
+
# { type: :network }
|
|
21
|
+
class ResourceLockManager
|
|
22
|
+
# 资源锁超时异常
|
|
23
|
+
class ResourceLockTimeout < StandardError; end
|
|
24
|
+
|
|
25
|
+
def initialize
|
|
26
|
+
@mutex = Mutex.new
|
|
27
|
+
@locked_resources = [] # [{ instance: ResourceInstance, task_id: String }]
|
|
28
|
+
@registry = TaskResources::ResourceRegistry.new
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# 尝试获取资源锁(非阻塞)
|
|
32
|
+
# @param resource_specs [Array<Hash>] 资源规格数组
|
|
33
|
+
# @param task_id [String] 任务ID
|
|
34
|
+
# @return [Boolean] 是否成功获取所有资源
|
|
35
|
+
def try_acquire(resource_specs, task_id)
|
|
36
|
+
return true if resource_specs.nil? || resource_specs.empty?
|
|
37
|
+
|
|
38
|
+
instances = parse_resource_specs(resource_specs)
|
|
39
|
+
|
|
40
|
+
@mutex.synchronize do
|
|
41
|
+
return false if has_conflicts?(instances)
|
|
42
|
+
|
|
43
|
+
# 锁定所有资源
|
|
44
|
+
instances.each do |instance|
|
|
45
|
+
@locked_resources << { instance: instance, task_id: task_id }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
true
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# 阻塞式获取资源锁(带超时)
|
|
53
|
+
# @param resource_specs [Array<Hash>] 资源规格数组
|
|
54
|
+
# @param task_id [String] 任务ID
|
|
55
|
+
# @param timeout [Integer] 超时时间(秒)
|
|
56
|
+
# @return [Boolean] 是否成功获取
|
|
57
|
+
def acquire_blocking(resource_specs, task_id, timeout: 30)
|
|
58
|
+
return true if resource_specs.nil? || resource_specs.empty?
|
|
59
|
+
|
|
60
|
+
deadline = Time.now + timeout
|
|
61
|
+
|
|
62
|
+
loop do
|
|
63
|
+
return true if try_acquire(resource_specs, task_id)
|
|
64
|
+
|
|
65
|
+
if Time.now >= deadline
|
|
66
|
+
return false # 超时
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
sleep(0.1) # 短暂等待后重试
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# 释放任务占用的所有资源
|
|
74
|
+
# @param task_id [String] 任务ID
|
|
75
|
+
def release(task_id)
|
|
76
|
+
@mutex.synchronize do
|
|
77
|
+
@locked_resources.delete_if { |lock| lock[:task_id] == task_id }
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# 释放任务占用的部分资源
|
|
82
|
+
# @param resource_specs [Array<Hash>] 要释放的资源规格
|
|
83
|
+
# @param task_id [String] 任务ID
|
|
84
|
+
def release_partial(resource_specs, task_id)
|
|
85
|
+
return if resource_specs.nil? || resource_specs.empty?
|
|
86
|
+
|
|
87
|
+
instances = parse_resource_specs(resource_specs)
|
|
88
|
+
|
|
89
|
+
@mutex.synchronize do
|
|
90
|
+
instances.each do |instance|
|
|
91
|
+
@locked_resources.delete_if do |lock|
|
|
92
|
+
lock[:task_id] == task_id && lock[:instance].matches?(instance)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# 检查资源是否可用
|
|
99
|
+
# @param resource_specs [Array<Hash>] 资源规格数组
|
|
100
|
+
# @return [Boolean]
|
|
101
|
+
def available?(resource_specs)
|
|
102
|
+
return true if resource_specs.nil? || resource_specs.empty?
|
|
103
|
+
|
|
104
|
+
instances = parse_resource_specs(resource_specs)
|
|
105
|
+
|
|
106
|
+
@mutex.synchronize do
|
|
107
|
+
!has_conflicts?(instances)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# 获取资源锁定状态(用于调试和监控)
|
|
112
|
+
# @return [Hash] 资源锁定状态信息
|
|
113
|
+
def lock_status
|
|
114
|
+
@mutex.synchronize do
|
|
115
|
+
{
|
|
116
|
+
total_locks: @locked_resources.size,
|
|
117
|
+
locks_by_task: group_locks_by_task,
|
|
118
|
+
locks_by_resource: group_locks_by_resource
|
|
119
|
+
}
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# 获取任务占用的资源列表
|
|
124
|
+
# @param task_id [String] 任务ID
|
|
125
|
+
# @return [Array<ResourceInstance>] 资源实例列表
|
|
126
|
+
def locked_resources(task_id)
|
|
127
|
+
@mutex.synchronize do
|
|
128
|
+
@locked_resources
|
|
129
|
+
.select { |lock| lock[:task_id] == task_id }
|
|
130
|
+
.map { |lock| lock[:instance] }
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
# 解析资源规格为资源实例
|
|
137
|
+
# @param specs [Array<Hash>] 资源规格数组
|
|
138
|
+
# @return [Array<ResourceInstance>]
|
|
139
|
+
def parse_resource_specs(specs)
|
|
140
|
+
specs = [specs] unless specs.is_a?(Array)
|
|
141
|
+
|
|
142
|
+
specs.map do |spec|
|
|
143
|
+
unless spec.is_a?(Hash) && spec[:type]
|
|
144
|
+
raise ArgumentError, "Invalid resource spec: #{spec.inspect}"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
type_obj = @registry.get(spec[:type])
|
|
148
|
+
context = spec.reject { |k, _| k == :type } # 除了 :type 之外的都是上下文
|
|
149
|
+
TaskResources::ResourceInstance.new(type_obj, context)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# 检查是否有冲突
|
|
154
|
+
# @param new_instances [Array<ResourceInstance>]
|
|
155
|
+
# @return [Boolean]
|
|
156
|
+
def has_conflicts?(new_instances)
|
|
157
|
+
new_instances.any? do |new_inst|
|
|
158
|
+
@locked_resources.any? do |lock|
|
|
159
|
+
new_inst.conflicts_with?(lock[:instance])
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# 按任务分组锁定信息
|
|
165
|
+
# @return [Hash] { task_id => [resource_instance, ...] }
|
|
166
|
+
def group_locks_by_task
|
|
167
|
+
result = Hash.new { |h, k| h[k] = [] }
|
|
168
|
+
|
|
169
|
+
@locked_resources.each do |lock|
|
|
170
|
+
result[lock[:task_id]] << lock[:instance].to_s
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
result
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# 按资源类型分组锁定信息
|
|
177
|
+
# @return [Hash] { resource_name => [task_id, ...] }
|
|
178
|
+
def group_locks_by_resource
|
|
179
|
+
result = Hash.new { |h, k| h[k] = [] }
|
|
180
|
+
|
|
181
|
+
@locked_resources.each do |lock|
|
|
182
|
+
resource_name = lock[:instance].type.name
|
|
183
|
+
result[resource_name] << lock[:task_id]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
result
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'pindo/base/funlog'
|
|
2
|
+
require_relative 'execution_strategy'
|
|
3
|
+
|
|
4
|
+
module Pindo
|
|
5
|
+
module TaskSystem
|
|
6
|
+
# SerialExecutionStrategy - 串行执行策略
|
|
7
|
+
#
|
|
8
|
+
# 职责:
|
|
9
|
+
# - 在主线程中按顺序执行所有任务
|
|
10
|
+
# - 处理依赖关系和阻塞情况
|
|
11
|
+
class SerialExecutionStrategy < ExecutionStrategy
|
|
12
|
+
# 执行任务
|
|
13
|
+
# @param task_manager [TaskManager] 任务管理器
|
|
14
|
+
def execute(task_manager)
|
|
15
|
+
queue = task_manager.queue
|
|
16
|
+
checker = task_manager.dependency_checker
|
|
17
|
+
executor = task_manager.executor
|
|
18
|
+
resource_manager = task_manager.resource_lock_manager
|
|
19
|
+
|
|
20
|
+
# 主循环:按顺序执行所有任务
|
|
21
|
+
while !queue.pending_empty?
|
|
22
|
+
# 先检查依赖(不在锁内)
|
|
23
|
+
pending_tasks = queue.pending_snapshot
|
|
24
|
+
executable_task = pending_tasks.find { |t| checker.dependencies_satisfied?(t) }
|
|
25
|
+
|
|
26
|
+
unless executable_task
|
|
27
|
+
# 检查是否所有任务都因依赖问题无法执行
|
|
28
|
+
if checker.all_pending_blocked?
|
|
29
|
+
Funlog.warning("所有剩余任务都因依赖问题无法执行")
|
|
30
|
+
break
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# 没有可执行的任务,短暂休眠后重试
|
|
34
|
+
sleep(0.1)
|
|
35
|
+
next
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# 原子操作:查找并获取资源锁
|
|
39
|
+
task = queue.find_and_remove do |t|
|
|
40
|
+
# 只检查是否是刚才找到的任务,并尝试获取资源
|
|
41
|
+
t.id == executable_task.id &&
|
|
42
|
+
resource_manager.try_acquire(t.required_resources, t.id)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# 如果获取失败(资源被占用),继续循环
|
|
46
|
+
next unless task
|
|
47
|
+
|
|
48
|
+
# 执行任务
|
|
49
|
+
executor.execute_task_sync(task, task_manager)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# 策略名称
|
|
54
|
+
# @return [String]
|
|
55
|
+
def name
|
|
56
|
+
"串行执行"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|