ryry-cli 6.16__tar.gz → 6.18__tar.gz

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 (33) hide show
  1. {ryry_cli-6.16/ryry_cli.egg-info → ryry_cli-6.18}/PKG-INFO +1 -3
  2. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/constant.py +2 -2
  3. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/main.py +8 -20
  4. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/ryry_server_socket.py +91 -15
  5. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/ryry_webapi.py +0 -10
  6. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/shared_memory.py +125 -31
  7. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/store.py +1 -1
  8. ryry_cli-6.18/ryry/taskUtils.py +496 -0
  9. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/upload.py +0 -28
  10. {ryry_cli-6.16 → ryry_cli-6.18/ryry_cli.egg-info}/PKG-INFO +1 -3
  11. {ryry_cli-6.16 → ryry_cli-6.18}/ryry_cli.egg-info/requires.txt +0 -3
  12. {ryry_cli-6.16 → ryry_cli-6.18}/setup.py +1 -2
  13. ryry_cli-6.16/ryry/taskUtils.py +0 -304
  14. {ryry_cli-6.16 → ryry_cli-6.18}/LICENSE +0 -0
  15. {ryry_cli-6.16 → ryry_cli-6.18}/README.md +0 -0
  16. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/__init__.py +0 -0
  17. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/daemon_base.py +0 -0
  18. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/daemon_manager.py +0 -0
  19. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/proxy_manager.py +0 -0
  20. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/ryry_service.py +0 -0
  21. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/ryry_widget.py +0 -0
  22. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/script_template/__init__.py +0 -0
  23. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/script_template/daemon.py +0 -0
  24. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/script_template/main.py +0 -0
  25. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/script_template/run.py +0 -0
  26. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/server_func.py +0 -0
  27. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/task.py +0 -0
  28. {ryry_cli-6.16 → ryry_cli-6.18}/ryry/utils.py +0 -0
  29. {ryry_cli-6.16 → ryry_cli-6.18}/ryry_cli.egg-info/SOURCES.txt +0 -0
  30. {ryry_cli-6.16 → ryry_cli-6.18}/ryry_cli.egg-info/dependency_links.txt +0 -0
  31. {ryry_cli-6.16 → ryry_cli-6.18}/ryry_cli.egg-info/entry_points.txt +0 -0
  32. {ryry_cli-6.16 → ryry_cli-6.18}/ryry_cli.egg-info/top_level.txt +0 -0
  33. {ryry_cli-6.16 → ryry_cli-6.18}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ryry-cli
3
- Version: 6.16
3
+ Version: 6.18
4
4
  Summary: ryry tools
5
5
  Home-page: https://github.com/dalipenMedia
6
6
  Author: dalipen
@@ -24,8 +24,6 @@ Requires-Dist: urlparser
24
24
  Requires-Dist: urllib3
25
25
  Requires-Dist: portalocker
26
26
  Requires-Dist: PyYAML
27
- Provides-Extra: with-mecord
28
- Requires-Dist: mecord-cli>=0.7.407; extra == "with-mecord"
29
27
 
30
28
  ryry Python Tool
31
29
  ===============================================
@@ -1,6 +1,6 @@
1
1
  #!!!!! do not change this file !!!!!
2
- app_version="6.16"
3
- app_bulld_anchor="Noh_2026-06-11 16:51:17.054964"
2
+ app_version="6.18"
3
+ app_bulld_anchor="Noh_2026-07-05 17:02:09.601187"
4
4
  app_name="ryry-cli"
5
5
  import sys, os
6
6
  if getattr(sys, 'frozen', False):
@@ -173,26 +173,14 @@ def status():
173
173
  deviceid = utils.generate_unique_id()
174
174
  import socket
175
175
  machine_name = socket.gethostname()
176
- service = ryry_service.ryryService()
177
- thread_num = store.get_multithread()
178
- def get_shared_memory_max_counter():
179
- # 获取系统推荐的共享目录
180
- system = platform.system()
181
- if system == 'Windows':
182
- temp_dir = os.environ.get('TEMP') or os.environ.get('TMP') or os.path.expanduser('~\\AppData\\Local\\Temp')
183
- data_dir = os.path.join(temp_dir, 'widget_shared_memory')
184
- elif system == 'Darwin':
185
- cache_dir = os.path.expanduser('~/Library/Caches')
186
- data_dir = os.path.join(cache_dir, 'widget_shared_memory')
187
- else:
188
- data_dir = '/tmp/widget_shared_memory'
189
- data_file = os.path.join(data_dir, 'widget_power.json')
190
- try:
191
- with open(data_file, 'r', encoding='utf-8') as f:
192
- data = json.load(f)
193
- return data.get('max_counter', None)
194
- except Exception:
195
- return None
176
+ service = ryry_service.ryryService()
177
+ thread_num = store.get_multithread()
178
+ def get_shared_memory_max_counter():
179
+ try:
180
+ from ryry.shared_memory import shared_memory_service
181
+ return shared_memory_service.get_max_counter()
182
+ except Exception:
183
+ return None
196
184
  shared_max_counter = get_shared_memory_max_counter()
197
185
  shared_max_counter_str = ""
198
186
  if shared_max_counter and shared_max_counter != thread_num:
@@ -17,8 +17,20 @@ class RyryTaskExecutor(Thread):
17
17
  ttt = 0
18
18
 
19
19
  def appendTask(self, data, callback):
20
- shared_memory_service.increment_cur_counter()
21
- self.task_queue.put([data, callback])
20
+ if not self.workers_started:
21
+ raise Exception("task workers are not ready")
22
+ counter_incremented = False
23
+ try:
24
+ shared_memory_service.increment_cur_counter()
25
+ counter_incremented = True
26
+ self.task_queue.put([data, callback])
27
+ except Exception:
28
+ if counter_incremented:
29
+ try:
30
+ shared_memory_service.decrement_cur_counter()
31
+ except:
32
+ pass
33
+ raise
22
34
  try:
23
35
  taskUUID = data["taskUUID"]
24
36
  taskUtils.taskPrint(taskUUID, f"{current_thread().name} === addQueue task : {taskUUID}")
@@ -27,6 +39,15 @@ class RyryTaskExecutor(Thread):
27
39
 
28
40
  def idlePower(self):
29
41
  return shared_memory_service.get_max_counter() - shared_memory_service.get_cur_counter()
42
+
43
+ def canAcceptTask(self):
44
+ if not self.workers_started:
45
+ return False
46
+ try:
47
+ return shared_memory_service.ensure_available()
48
+ except Exception as e:
49
+ taskUtils.taskPrint(None, f"{current_thread().name} === shared memory unavailable : {e}")
50
+ return False
30
51
 
31
52
  def widgetHasPower(self, cur_widget_id):
32
53
  widget_power = shared_memory_service.get_widget_power(cur_widget_id)
@@ -58,6 +79,7 @@ class RyryTaskExecutor(Thread):
58
79
  self.max_counter = store.get_multithread()
59
80
  self.is_running = True
60
81
  self.THEADING_LIST = []
82
+ self.workers_started = False
61
83
  self.start()
62
84
 
63
85
  def taskRunning(self):
@@ -67,6 +89,14 @@ class RyryTaskExecutor(Thread):
67
89
  data, callback = self.task_queue.get()
68
90
  if data is None:
69
91
  break
92
+ taskUUID = data.get("taskUUID", "")
93
+ widget_id = None
94
+ task_record_added = False
95
+ widget_power_added = False
96
+ callback_needed = False
97
+ is_ok = False
98
+ msg = ""
99
+ result = "{}"
70
100
  try:
71
101
  data_config = json.loads(data["config"])
72
102
  widget_id = data_config["widget_id"]
@@ -76,34 +106,55 @@ class RyryTaskExecutor(Thread):
76
106
  timeout = data_config.get("timeout", 600)
77
107
  if "timeout" in data:
78
108
  timeout = data["timeout"]
79
-
80
- taskUUID = data["taskUUID"]
81
109
  domain = ""
82
110
  params_tmp = json.loads(data["data"])
83
111
  if "domain" in params_tmp:
84
112
  domain = params_tmp["domain"]
85
113
  taskUtils.taskPrint(taskUUID, f"{current_thread().name} === receive task : {taskUUID}")
86
114
  _appendTask(taskUUID, domain)
115
+ task_record_added = True
87
116
  self.addWidgetPower(widget_id, max_task_number)
117
+ widget_power_added = True
88
118
  is_ok, msg, result = task.runTask(data, timeout)
119
+ callback_needed = True
89
120
  if is_ok == False:
90
121
  taskUtils.notifyTaskFail(taskUUID, msg)
91
- shared_memory_service.decrement_cur_counter()
92
- callback(taskUUID, is_ok, msg, result)
93
- _removeTask(taskUUID)
94
- self.delWidgetPower(widget_id)
95
122
  except Exception as e:
96
- taskUtils.taskPrint(taskUUID, f"{current_thread().name} === task exception : {e}")
97
- taskUtils.notifyScriptError(taskUUID)
123
+ msg = f"{current_thread().name} === task exception : {e}"
124
+ result = "{}"
125
+ callback_needed = bool(taskUUID)
126
+ taskUtils.taskPrint(taskUUID, msg)
127
+ if taskUUID:
128
+ taskUtils.notifyScriptError(taskUUID)
98
129
  finally:
99
- taskUtils.taskPrint(taskUUID, None)
130
+ try:
131
+ shared_memory_service.decrement_cur_counter()
132
+ except Exception as e:
133
+ taskUtils.taskPrint(taskUUID, f"{current_thread().name} === decrement counter exception : {e}")
134
+ if callback_needed and taskUUID:
135
+ try:
136
+ callback(taskUUID, is_ok, msg, result)
137
+ except Exception as e:
138
+ taskUtils.taskPrint(taskUUID, f"{current_thread().name} === task callback exception : {e}")
139
+ if task_record_added:
140
+ try:
141
+ _removeTask(taskUUID)
142
+ except Exception as e:
143
+ taskUtils.taskPrint(taskUUID, f"{current_thread().name} === remove task exception : {e}")
144
+ if widget_power_added:
145
+ try:
146
+ self.delWidgetPower(widget_id)
147
+ except Exception as e:
148
+ taskUtils.taskPrint(taskUUID, f"{current_thread().name} === widget power cleanup exception : {e}")
149
+ if taskUUID:
150
+ taskUtils.taskPrint(taskUUID, None)
100
151
  self.task_queue.task_done()
101
152
  except Exception as ex:
102
153
  taskUtils.taskPrint(None, f"{current_thread().name} === exception : {ex}")
103
154
  pass
104
155
  print(f" {current_thread().name} taskRunning stop")
105
156
 
106
- def run(self):
157
+ def startTaskWorkers(self):
107
158
  shared_memory_service.start_service()
108
159
  shared_memory_service.set_max_counter(self.max_counter)
109
160
  shared_memory_service.set_cur_counter(0)
@@ -111,7 +162,18 @@ class RyryTaskExecutor(Thread):
111
162
  self.THEADING_LIST.append(Thread(name=f"exector-{idx}",target=self.taskRunning))
112
163
  for t in self.THEADING_LIST:
113
164
  t.start()
165
+ self.workers_started = True
166
+
167
+ def run(self):
114
168
  while self.is_running:
169
+ if not self.workers_started:
170
+ try:
171
+ self.startTaskWorkers()
172
+ print(f" {self.name} workers started")
173
+ except Exception as e:
174
+ taskUtils.taskPrint(None, f"{self.name} === start workers exception : {e}")
175
+ time.sleep(5)
176
+ continue
115
177
  time.sleep(5)
116
178
  for t in self.THEADING_LIST:
117
179
  t.join()
@@ -123,7 +185,10 @@ class RyryTaskExecutor(Thread):
123
185
  for _ in range(self.max_counter*2):
124
186
  self.task_queue.put([None, None])
125
187
  print(f" {self.name} stop")
126
- shared_memory_service.stop_service()
188
+ try:
189
+ shared_memory_service.stop_service()
190
+ except Exception as e:
191
+ taskUtils.taskPrint(None, f"{self.name} === stop shared memory exception : {e}")
127
192
 
128
193
  lock = Lock()
129
194
  task_config_file = os.path.join(constant.base_path, f"task_config.txt")
@@ -238,6 +303,9 @@ class RyryShortConnectThread(Thread):
238
303
  if self.executor.idlePower() <= 0:
239
304
  time.sleep(1)
240
305
  continue
306
+ if not self.executor.canAcceptTask():
307
+ time.sleep(wait_time)
308
+ continue
241
309
 
242
310
  widget_list = {}
243
311
  map = store.widgetMap()
@@ -250,7 +318,15 @@ class RyryShortConnectThread(Thread):
250
318
  widget_list[it] = map[it].get("version", "0.0")
251
319
  datas = ryry_webapi.GetTask(widget_list)
252
320
  for it in datas:
253
- self.executor.appendTask(it, self.taskCallback)
321
+ try:
322
+ self.executor.appendTask(it, self.taskCallback)
323
+ except Exception as e:
324
+ taskUUID = it.get("taskUUID", "")
325
+ msg = f"{self.name} === append task exception : {e}"
326
+ taskUtils.taskPrint(taskUUID, msg)
327
+ if taskUUID:
328
+ taskUtils.notifyScriptError(taskUUID)
329
+ self.taskCallback(taskUUID, False, msg, "{}")
254
330
 
255
331
  # if task empty -> full, set wait_time = max -> min
256
332
  if len(datas) > 0:
@@ -281,4 +357,4 @@ class RyryShortConnectThread(Thread):
281
357
 
282
358
  def markStop(self):
283
359
  print(f" {self.name} waiting stop")
284
- self.is_running = False
360
+ self.is_running = False
@@ -311,16 +311,6 @@ def upload(localFilePath, ext, keepItAlways=False, needTranscode=False, addition
311
311
  return _upload(localFilePath, ext, keepItAlways=keepItAlways)
312
312
  except:
313
313
  pass
314
- from mecord import upload as mecord_upload
315
- try:
316
- url = mecord_upload.upload(localFilePath,
317
- None,
318
- "sg",
319
- needTranscode=needTranscode,
320
- needAddtionUrl=True) #mecord会使用签名信息,强制needAddtionUrl
321
- return _changeDomain(url, "m.mecordai.com")
322
- except:
323
- pass
324
314
  def upload_oss(localFilePath, ext, bucket, endpoint):
325
315
  import hashlib
326
316
  import hmac
@@ -2,7 +2,6 @@ import os
2
2
  import json
3
3
  import time
4
4
  import threading
5
- import tempfile
6
5
  import platform
7
6
  import subprocess
8
7
  from threading import Lock
@@ -28,17 +27,20 @@ class SharedMemoryService:
28
27
  def __init__(self):
29
28
  self.service_name = "widget_power"
30
29
  # 使用系统推荐目录,确保多进程共享
31
- self.data_dir = self._get_system_shared_dir()
32
- self.data_file = os.path.join(self.data_dir, f"{self.service_name}.json")
33
- self.lock_file = os.path.join(self.data_dir, f"{self.service_name}.lock")
34
- self.process_count_file = os.path.join(self.data_dir, f"{self.service_name}_process_count.json")
35
- self.process_count_lock_file = self.process_count_file + '.lock'
30
+ self._set_data_dir(self._get_system_shared_dir())
36
31
  self.current_service_name = os.path.basename(sys.argv[0])
37
- # 确保数据目录存在
32
+ # 确保共享目录存在;锁文件保持固定路径,供其他程序共同读写
38
33
  os.makedirs(self.data_dir, exist_ok=True)
39
34
 
40
35
  # 增加启动标志位,避免重复初始化
41
36
  self._started = False
37
+
38
+ def _set_data_dir(self, data_dir: str):
39
+ self.data_dir = data_dir
40
+ self.data_file = os.path.join(self.data_dir, f"{self.service_name}.json")
41
+ self.lock_file = os.path.join(self.data_dir, f"{self.service_name}.lock")
42
+ self.process_count_file = os.path.join(self.data_dir, f"{self.service_name}_process_count.json")
43
+ self.process_count_lock_file = self.process_count_file + '.lock'
42
44
 
43
45
  def _get_system_shared_dir(self) -> str:
44
46
  """获取系统推荐的共享目录"""
@@ -57,6 +59,86 @@ class SharedMemoryService:
57
59
  else: # Linux 和其他 Unix-like 系统
58
60
  # Linux: 使用 /tmp 或 /var/tmp
59
61
  return '/tmp/widget_shared_memory'
62
+
63
+ def _chmod_if_possible(self, path: str, mode: int):
64
+ if platform.system() == 'Windows':
65
+ return
66
+ try:
67
+ os.chmod(path, mode)
68
+ except (PermissionError, FileNotFoundError):
69
+ pass
70
+
71
+ def _shared_dir_mode(self) -> int:
72
+ if platform.system() == 'Linux':
73
+ return 0o1777
74
+ return 0o700
75
+
76
+ def _shared_file_mode(self) -> int:
77
+ if platform.system() == 'Linux':
78
+ return 0o666
79
+ return 0o600
80
+
81
+ def _path_permission_info(self, path: str) -> str:
82
+ try:
83
+ st = os.stat(path)
84
+ return f"mode={oct(st.st_mode & 0o7777)} uid={st.st_uid} gid={st.st_gid}"
85
+ except OSError as e:
86
+ return f"stat failed: {e}"
87
+
88
+ def _assert_shared_dir_writable(self):
89
+ if os.access(self.data_dir, os.W_OK | os.X_OK):
90
+ return
91
+ raise PermissionError(
92
+ f"shared memory dir is not writable: {self.data_dir} "
93
+ f"({self._path_permission_info(self.data_dir)}). "
94
+ f"fix: sudo chown -R $(id -u):$(id -g) {self.data_dir} "
95
+ f"or sudo chmod 1777 {self.data_dir}"
96
+ )
97
+
98
+ def _prepare_existing_shared_file(self, path: str):
99
+ if os.path.isdir(path):
100
+ raise PermissionError(f"shared memory file path is a directory: {path}")
101
+ if os.path.exists(path):
102
+ self._chmod_if_possible(path, self._shared_file_mode())
103
+
104
+ def _touch_shared_file(self, path: str):
105
+ self._prepare_existing_shared_file(path)
106
+ with open(path, 'a+', encoding='utf-8'):
107
+ pass
108
+ self._chmod_if_possible(path, self._shared_file_mode())
109
+
110
+ def _open_lock_file(self, path: str):
111
+ self._ensure_shared_storage()
112
+ return open(path, 'a+', encoding='utf-8')
113
+
114
+ def _ensure_shared_storage(self):
115
+ """确保固定共享目录和锁文件可写"""
116
+ os.makedirs(self.data_dir, mode=self._shared_dir_mode(), exist_ok=True)
117
+ self._chmod_if_possible(self.data_dir, self._shared_dir_mode())
118
+ self._assert_shared_dir_writable()
119
+ self._touch_shared_file(self.lock_file)
120
+ self._touch_shared_file(self.process_count_lock_file)
121
+ self._prepare_existing_shared_file(self.data_file)
122
+ self._prepare_existing_shared_file(self.process_count_file)
123
+
124
+ def _default_data(self) -> Dict[str, Any]:
125
+ return {
126
+ "max_counter": 999,
127
+ "cur_counter": 0,
128
+ "widget_power": {},
129
+ "last_update": time.time()
130
+ }
131
+
132
+ def _validate_counter_data(self, data: Any) -> bool:
133
+ if not isinstance(data, dict):
134
+ return False
135
+ if not isinstance(data.get("max_counter", 999), int):
136
+ return False
137
+ if not isinstance(data.get("cur_counter", 0), int):
138
+ return False
139
+ if not isinstance(data.get("widget_power", {}), dict):
140
+ return False
141
+ return True
60
142
 
61
143
  def _acquire_shared_lock(self, file_obj):
62
144
  """获取共享锁(跨平台)"""
@@ -102,9 +184,10 @@ class SharedMemoryService:
102
184
  def operation(data):
103
185
  # 验证数据完整性
104
186
  if not self._validate_process_data(data):
105
- data = {"processes": {}}
187
+ data.clear()
188
+ data["processes"] = {}
106
189
 
107
- # 清理死亡进程(只清理本服务的)
190
+ # 清理共享文件中的死亡进程,避免旧状态长期占用任务槽
108
191
  self._cleanup_dead_processes_internal(data, current_time, skip_pid=current_pid)
109
192
 
110
193
  # 检查当前进程是否已经注册
@@ -121,7 +204,7 @@ class SharedMemoryService:
121
204
  return len(data["processes"])
122
205
 
123
206
  # 原子性地注册进程
124
- with open(self.process_count_lock_file, 'w') as lock_f:
207
+ with self._open_lock_file(self.process_count_lock_file) as lock_f:
125
208
  self._acquire_exclusive_lock(lock_f)
126
209
  try:
127
210
  if os.path.exists(self.process_count_file):
@@ -132,6 +215,7 @@ class SharedMemoryService:
132
215
  data = {"processes": {}}
133
216
  else:
134
217
  data = {"processes": {}}
218
+ data = self._normalize_process_data(data)
135
219
 
136
220
  process_count = operation(data)
137
221
 
@@ -170,11 +254,7 @@ class SharedMemoryService:
170
254
  if skip_pid is not None and pid == skip_pid:
171
255
  continue # 跳过当前进程
172
256
 
173
- # 只清理本服务的进程
174
- if info.get("service_name") != self.current_service_name:
175
- continue
176
-
177
- # 检测1: 检查进程是否真的存在
257
+ # 检查进程是否真的存在;其他服务的死进程也要清理,避免共享计数长期卡住
178
258
  if not self._is_process_alive(pid):
179
259
  dead_processes.append(pid_str)
180
260
  continue
@@ -197,9 +277,11 @@ class SharedMemoryService:
197
277
  def operation(data):
198
278
  # 验证数据完整性
199
279
  if not self._validate_process_data(data):
280
+ data.clear()
281
+ data["processes"] = {}
200
282
  return 0
201
283
 
202
- # 清理死亡进程(只清理本服务的)
284
+ # 清理共享文件中的死亡进程,避免旧状态长期占用任务槽
203
285
  self._cleanup_dead_processes_internal(data, current_time, skip_pid=current_pid)
204
286
 
205
287
  # 注销当前进程(只注销本服务的)
@@ -209,7 +291,7 @@ class SharedMemoryService:
209
291
  return len(data["processes"])
210
292
 
211
293
  # 原子性地注销进程
212
- with open(self.process_count_lock_file, 'w') as lock_f:
294
+ with self._open_lock_file(self.process_count_lock_file) as lock_f:
213
295
  self._acquire_exclusive_lock(lock_f)
214
296
  try:
215
297
  if os.path.exists(self.process_count_file):
@@ -217,7 +299,8 @@ class SharedMemoryService:
217
299
  with open(self.process_count_file, 'r', encoding='utf-8') as f:
218
300
  data = json.load(f)
219
301
  except (json.JSONDecodeError, UnicodeDecodeError, FileNotFoundError):
220
- return 0
302
+ data = {"processes": {}}
303
+ data = self._normalize_process_data(data)
221
304
 
222
305
  remaining_processes = operation(data)
223
306
 
@@ -241,11 +324,10 @@ class SharedMemoryService:
241
324
  return 0
242
325
 
243
326
  # 验证数据完整性
244
- if not self._validate_process_data(data):
245
- return 0
327
+ data = self._normalize_process_data(data)
246
328
 
247
329
  # 清理死亡进程
248
- with open(self.process_count_lock_file, 'w') as lock_f:
330
+ with self._open_lock_file(self.process_count_lock_file) as lock_f:
249
331
  self._acquire_exclusive_lock(lock_f)
250
332
  try:
251
333
  self._cleanup_dead_processes_internal(data, time.time())
@@ -264,6 +346,8 @@ class SharedMemoryService:
264
346
 
265
347
  def operation(data):
266
348
  if "processes" not in data:
349
+ data.clear()
350
+ data["processes"] = {}
267
351
  return 0
268
352
 
269
353
  dead_processes = []
@@ -290,7 +374,7 @@ class SharedMemoryService:
290
374
  return len(data["processes"])
291
375
 
292
376
  # 原子性地清理死亡进程
293
- with open(self.process_count_lock_file, 'w') as lock_f:
377
+ with self._open_lock_file(self.process_count_lock_file) as lock_f:
294
378
  self._acquire_exclusive_lock(lock_f)
295
379
  try:
296
380
  if os.path.exists(self.process_count_file):
@@ -299,6 +383,7 @@ class SharedMemoryService:
299
383
  data = json.load(f)
300
384
  except (json.JSONDecodeError, UnicodeDecodeError, FileNotFoundError):
301
385
  data = {"processes": {}}
386
+ data = self._normalize_process_data(data)
302
387
 
303
388
  remaining_processes = operation(data)
304
389
 
@@ -358,6 +443,11 @@ class SharedMemoryService:
358
443
 
359
444
  except Exception:
360
445
  return False
446
+
447
+ def _normalize_process_data(self, data: Any) -> Dict[str, Any]:
448
+ if self._validate_process_data(data):
449
+ return data
450
+ return {"processes": {}}
361
451
 
362
452
  def _update_heartbeat(self):
363
453
  """更新当前进程的心跳(简化版,仅保持数据结构一致性)"""
@@ -370,7 +460,7 @@ class SharedMemoryService:
370
460
  return len(data.get("processes", {}))
371
461
 
372
462
  # 原子性地更新心跳
373
- with open(self.process_count_lock_file, 'w') as lock_f:
463
+ with self._open_lock_file(self.process_count_lock_file) as lock_f:
374
464
  self._acquire_exclusive_lock(lock_f)
375
465
  try:
376
466
  if os.path.exists(self.process_count_file):
@@ -379,6 +469,7 @@ class SharedMemoryService:
379
469
  data = json.load(f)
380
470
  except (json.JSONDecodeError, UnicodeDecodeError, FileNotFoundError):
381
471
  data = {"processes": {}}
472
+ data = self._normalize_process_data(data)
382
473
 
383
474
  operation(data)
384
475
 
@@ -424,15 +515,12 @@ class SharedMemoryService:
424
515
  data = json.load(f)
425
516
  finally:
426
517
  self._release_lock(f)
518
+ if not self._validate_counter_data(data):
519
+ return self._default_data()
427
520
  return data
428
- except (FileNotFoundError, json.JSONDecodeError):
521
+ except (FileNotFoundError, json.JSONDecodeError, UnicodeDecodeError):
429
522
  # 如果文件不存在或损坏,返回默认数据
430
- return {
431
- "max_counter": 999,
432
- "cur_counter": 0,
433
- "widget_power": {},
434
- "last_update": time.time()
435
- }
523
+ return self._default_data()
436
524
 
437
525
  def _write_data(self, data: Dict[str, Any]):
438
526
  """写入数据文件"""
@@ -459,7 +547,7 @@ class SharedMemoryService:
459
547
  def _atomic_operation(self, operation):
460
548
  """执行原子操作(多进程安全)"""
461
549
  # 使用文件锁确保多进程间的原子性
462
- with open(self.lock_file, 'w') as lock_f:
550
+ with self._open_lock_file(self.lock_file) as lock_f:
463
551
  # 获取独占锁,阻塞直到获得锁
464
552
  self._acquire_exclusive_lock(lock_f)
465
553
  try:
@@ -469,6 +557,12 @@ class SharedMemoryService:
469
557
  return result
470
558
  finally:
471
559
  self._release_lock(lock_f)
560
+
561
+ def ensure_available(self) -> bool:
562
+ """确认共享计数文件可读写,避免拉到任务后本地无法入队"""
563
+ def operation(data):
564
+ return True
565
+ return self._atomic_operation(operation)
472
566
 
473
567
  def start_service(self):
474
568
  """启动共享内存服务(轻量级,无需额外进程)"""
@@ -177,7 +177,7 @@ def extend():
177
177
  _genExtend()
178
178
  elif read_data["extend"].get("device_id", "") != read_data.get("deviceInfo", {}).get("device_id", ""):
179
179
  _genExtend()
180
- elif read_data["extend"].get("app", "") == "ryry-cli 6.16":
180
+ elif read_data["extend"].get("app", "") == "ryry-cli 6.18":
181
181
  _genExtend()
182
182
  elif any(k not in read_data["extend"] for k in ["device_id", "host_name", "cpu", "memory", "disk", "gpu"]):
183
183
  _genExtend()