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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pindo/base/funlog.rb +62 -5
  3. data/lib/pindo/base/git_handler.rb +83 -22
  4. data/lib/pindo/base/output_sink.rb +69 -0
  5. data/lib/pindo/command/android/autobuild.rb +57 -8
  6. data/lib/pindo/command/appstore/autobuild.rb +10 -1
  7. data/lib/pindo/command/ios/autobuild.rb +59 -7
  8. data/lib/pindo/command/jps/media.rb +164 -13
  9. data/lib/pindo/command/jps/upload.rb +14 -9
  10. data/lib/pindo/command/unity/autobuild.rb +64 -10
  11. data/lib/pindo/command/utils/tag.rb +9 -1
  12. data/lib/pindo/command/web/autobuild.rb +59 -10
  13. data/lib/pindo/module/android/android_build_helper.rb +6 -7
  14. data/lib/pindo/module/build/git_repo_helper.rb +29 -25
  15. data/lib/pindo/module/pgyer/pgyerhelper.rb +174 -77
  16. data/lib/pindo/module/task/core/concurrent_execution_strategy.rb +237 -0
  17. data/lib/pindo/module/task/core/dependency_checker.rb +123 -0
  18. data/lib/pindo/module/task/core/execution_strategy.rb +61 -0
  19. data/lib/pindo/module/task/core/resource_lock_manager.rb +190 -0
  20. data/lib/pindo/module/task/core/serial_execution_strategy.rb +60 -0
  21. data/lib/pindo/module/task/core/task_executor.rb +131 -0
  22. data/lib/pindo/module/task/core/task_queue.rb +221 -0
  23. data/lib/pindo/module/task/model/build/android_build_dev_task.rb +1 -1
  24. data/lib/pindo/module/task/model/build/android_build_task.rb +6 -2
  25. data/lib/pindo/module/task/model/build/ios_build_dev_task.rb +2 -3
  26. data/lib/pindo/module/task/model/build/ios_build_task.rb +6 -0
  27. data/lib/pindo/module/task/model/build_task.rb +22 -0
  28. data/lib/pindo/module/task/model/git/git_commit_task.rb +11 -2
  29. data/lib/pindo/module/task/model/git_task.rb +6 -0
  30. data/lib/pindo/module/task/model/jps/jps_message_task.rb +9 -11
  31. data/lib/pindo/module/task/model/jps/jps_upload_media_task.rb +204 -103
  32. data/lib/pindo/module/task/model/jps_task.rb +0 -1
  33. data/lib/pindo/module/task/model/unity_task.rb +38 -2
  34. data/lib/pindo/module/task/output/multi_line_output_manager.rb +380 -0
  35. data/lib/pindo/module/task/output/multi_line_task_display.rb +185 -0
  36. data/lib/pindo/module/task/output/stdout_redirector.rb +95 -0
  37. data/lib/pindo/module/task/pindo_task.rb +133 -9
  38. data/lib/pindo/module/task/task_manager.rb +98 -268
  39. data/lib/pindo/module/task/task_reporter.rb +135 -0
  40. data/lib/pindo/module/task/task_resources/resource_instance.rb +90 -0
  41. data/lib/pindo/module/task/task_resources/resource_registry.rb +105 -0
  42. data/lib/pindo/module/task/task_resources/resource_type.rb +59 -0
  43. data/lib/pindo/module/task/task_resources/types/directory_based_resource.rb +63 -0
  44. data/lib/pindo/module/task/task_resources/types/global_exclusive_resource.rb +33 -0
  45. data/lib/pindo/module/task/task_resources/types/global_shared_resource.rb +34 -0
  46. data/lib/pindo/module/xcode/xcode_build_helper.rb +26 -8
  47. data/lib/pindo/options/groups/jps_options.rb +10 -0
  48. data/lib/pindo/options/groups/task_options.rb +39 -0
  49. data/lib/pindo/version.rb +3 -2
  50. 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