sycommon-python-lib 0.2.6a2__py3-none-any.whl → 0.2.6a3__py3-none-any.whl

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.
@@ -0,0 +1,175 @@
1
+ """
2
+ 一致性哈希沙箱路由
3
+
4
+ 虚拟槽位层:将 0-10000 的虚拟槽位通过一致性哈希环映射到真实沙箱实例。
5
+ 用户到虚拟槽位的映射永久固定(只依赖 user_id),增减真实沙箱只影响槽位到实例的映射。
6
+
7
+ 架构:
8
+ ┌────────────────────────────────────────────────────────────┐
9
+ │ 用户请求 │
10
+ │ │
11
+ │ user_id ──MD5──▶ 虚拟槽位号 (0-10000) │
12
+ │ │ │
13
+ │ ▼ │
14
+ │ ┌─────────────────────────────────────────────────────┐ │
15
+ │ │ 一致性哈希环 │ │
16
+ │ │ │ │
17
+ │ │ 每个真实实例在环上占 150 个虚拟节点 │ │
18
+ │ │ 槽位号顺时针找到最近的虚拟节点 → 真实实例 │ │
19
+ │ └─────────────────────────────────────────────────────┘ │
20
+ │ │ │
21
+ │ ▼ │
22
+ │ ┌─────────────────────────────────────────────────────┐ │
23
+ │ │ 真实沙箱实例 │ │
24
+ │ │ 192.168.1.100:8080 (实例1) │ │
25
+ │ │ 192.168.1.101:8080 (实例2) │ │
26
+ │ │ 192.168.1.102:8080 (实例3) │ │
27
+ │ └─────────────────────────────────────────────────────┘ │
28
+ │ │
29
+ │ 增减实例时:仅相邻槽位重新分配,绝大部分用户不受影响 │
30
+ └────────────────────────────────────────────────────────────┘
31
+ """
32
+
33
+ import hashlib
34
+ from bisect import bisect_right
35
+ from typing import Optional
36
+
37
+ from sycommon.logging.kafka_log import SYLogger
38
+
39
+ # 虚拟槽位数量:0 到 VIRTUAL_SLOT_COUNT-1
40
+ VIRTUAL_SLOT_COUNT = 10001 # 0-10000
41
+
42
+ # 每个真实实例在哈希环上的虚拟节点数(越多分布越均匀)
43
+ DEFAULT_VIRTUAL_NODES_PER_INSTANCE = 500
44
+
45
+
46
+ class ConsistentHashMap:
47
+ """一致性哈希环,将虚拟槽位号映射到真实沙箱实例。
48
+
49
+ 使用虚拟节点技术保证负载均衡均匀。
50
+ 线程安全:build() 后 ring 为不可变结构,get_instance() 无需加锁。
51
+ """
52
+
53
+ def __init__(self, virtual_nodes_per_instance: int = DEFAULT_VIRTUAL_NODES_PER_INSTANCE):
54
+ self._ring: list[int] = [] # 排序的哈希环(虚拟节点的哈希值)
55
+ self._node_map: dict[int, str] = {} # ring_hash -> instance_key (ip:port)
56
+ self._virtual_nodes = virtual_nodes_per_instance
57
+
58
+ def build(self, instances: list[dict]) -> None:
59
+ """从 Nacos 实例列表构建一致性哈希环。
60
+
61
+ Args:
62
+ instances: Nacos 实例列表,每个元素包含 'ip' 和 'port' 字段
63
+ """
64
+ self._ring = []
65
+ self._node_map = {}
66
+
67
+ for ins in instances:
68
+ key = f"{ins['ip']}:{ins['port']}"
69
+ for i in range(self._virtual_nodes):
70
+ # 虚拟节点标识
71
+ vnode_key = f"{key}#vn{i}"
72
+ h = self._hash(vnode_key)
73
+ self._ring.append(h)
74
+ self._node_map[h] = key
75
+
76
+ self._ring.sort()
77
+ SYLogger.info(
78
+ f"[ConsistentHash] 哈希环构建完成: "
79
+ f"{len(instances)} 个实例, {len(self._ring)} 个虚拟节点"
80
+ )
81
+
82
+ def get_instance(self, slot: int) -> Optional[str]:
83
+ """虚拟槽位号 → 最近的真实实例 key (ip:port)。
84
+
85
+ 在哈希环上顺时针查找最近的虚拟节点。
86
+ 如果 slot 对应的位置之后没有节点,回到环首。
87
+
88
+ Args:
89
+ slot: 虚拟槽位号 (0 ~ VIRTUAL_SLOT_COUNT-1)
90
+
91
+ Returns:
92
+ 实例 key "ip:port",环为空时返回 None
93
+ """
94
+ if not self._ring:
95
+ return None
96
+
97
+ # 将 slot 映射到哈希环上的一个位置
98
+ h = self._hash(str(slot))
99
+ # 顺时针找到第一个 >= h 的节点
100
+ idx = bisect_right(self._ring, h)
101
+ if idx >= len(self._ring):
102
+ idx = 0 # 回到环首
103
+ return self._node_map[self._ring[idx]]
104
+
105
+ @staticmethod
106
+ def _hash(key: str) -> int:
107
+ """将字符串映射到 0 ~ 2^64-1 的哈希空间"""
108
+ return int(hashlib.md5(key.encode()).hexdigest(), 16) % (2 ** 64)
109
+
110
+
111
+ def user_to_slot(user_id: str) -> int:
112
+ """用户 ID → 固定虚拟槽位号。
113
+
114
+ 同一 user_id 永远返回相同的槽位号。
115
+ 槽位号范围: 0 ~ VIRTUAL_SLOT_COUNT-1
116
+
117
+ Args:
118
+ user_id: 用户 ID
119
+
120
+ Returns:
121
+ 虚拟槽位号 (int)
122
+ """
123
+ hash_value = int(hashlib.md5(user_id.encode()).hexdigest(), 16)
124
+ return hash_value % VIRTUAL_SLOT_COUNT
125
+
126
+
127
+ def resolve_user_instance(user_id: str, instances: list[dict]) -> dict:
128
+ """用户 ID + 可用实例列表 → 目标真实实例。
129
+
130
+ 完整路由流程:
131
+ 1. user_id → 虚拟槽位号(永久固定)
132
+ 2. 构建一致性哈希环
133
+ 3. 槽位号 → 真实实例
134
+
135
+ Args:
136
+ user_id: 用户 ID
137
+ instances: Nacos 可用实例列表,每个元素包含 'ip' 和 'port'
138
+
139
+ Returns:
140
+ 目标实例 dict(含 'ip', 'port' 等字段)
141
+
142
+ Raises:
143
+ RuntimeError: 实例列表为空
144
+ """
145
+ if not instances:
146
+ raise RuntimeError("实例列表为空,无法路由")
147
+
148
+ # 单实例直接返回,无需哈希
149
+ if len(instances) == 1:
150
+ return instances[0]
151
+
152
+ slot = user_to_slot(user_id)
153
+ ring = ConsistentHashMap()
154
+ ring.build(instances)
155
+ target_key = ring.get_instance(slot)
156
+
157
+ if target_key is None:
158
+ # 兜底:环构建失败时取第一个实例
159
+ SYLogger.warning(
160
+ f"[ConsistentHash] 哈希环为空,回退到第一个实例")
161
+ return instances[0]
162
+
163
+ # 查找目标实例
164
+ for ins in instances:
165
+ if f"{ins['ip']}:{ins['port']}" == target_key:
166
+ SYLogger.info(
167
+ f"[ConsistentHash] 路由: user_id={user_id} -> "
168
+ f"slot={slot} -> instance={target_key}"
169
+ )
170
+ return ins
171
+
172
+ # 未匹配(理论上不会发生),兜底
173
+ SYLogger.warning(
174
+ f"[ConsistentHash] 未找到匹配实例 key={target_key},回退到第一个实例")
175
+ return instances[0]
@@ -316,13 +316,10 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
316
316
  if not instances:
317
317
  raise RuntimeError(f"服务 [{service_name}] 无可用实例(已过滤非所有者本地沙箱)")
318
318
 
319
- # 基于用户ID的负载均衡:使用哈希选择实例
319
+ # 基于用户ID的一致性哈希负载均衡:用户固定映射到虚拟槽位,槽位映射到真实实例
320
320
  if load_balance and len(instances) > 1:
321
- hash_value = int(hashlib.md5(user_id.encode()).hexdigest(), 16)
322
- instance_index = hash_value % len(instances)
323
- instance = instances[instance_index]
324
- SYLogger.info(
325
- f"[Sandbox] 负载均衡: user_id={user_id} -> instance[{instance_index}/{len(instances)}]")
321
+ from sycommon.agent.sandbox.consistent_hash import resolve_user_instance
322
+ instance = resolve_user_instance(user_id, instances)
326
323
  else:
327
324
  instance = instances[0]
328
325
 
@@ -0,0 +1,279 @@
1
+ """
2
+ 一致性哈希沙箱路由 - 验证测试
3
+
4
+ 验证内容:
5
+ 1. 用户粘性:同一 user_id 多次调用返回相同实例
6
+ 2. 增实例:新增沙箱后只有部分用户迁移
7
+ 3. 删实例:删除沙箱后受影响用户被分散
8
+ 4. 均匀分布:10000 虚拟槽位均匀分配到各实例
9
+ 5. 虚拟槽位固定:user_id 到槽位号永久不变
10
+ """
11
+
12
+ import sys
13
+ import os
14
+ import random
15
+ import hashlib
16
+ from collections import Counter
17
+
18
+ # 抑制 SYLogger 日志
19
+ os.environ["LOG_LEVEL"] = "ERROR"
20
+
21
+ # 添加路径
22
+ sys.path.insert(0, "/Users/osulcode.xiao/miniconda3/lib/python3.11/site-packages")
23
+
24
+ from sycommon.agent.sandbox.consistent_hash import (
25
+ ConsistentHashMap,
26
+ VIRTUAL_SLOT_COUNT,
27
+ user_to_slot,
28
+ resolve_user_instance,
29
+ )
30
+
31
+
32
+ def make_instances(n: int) -> list[dict]:
33
+ """生成 n 个模拟沙箱实例"""
34
+ return [{"ip": f"10.0.0.{i+1}", "port": 8080} for i in range(n)]
35
+
36
+
37
+ def instance_key(ins: dict) -> str:
38
+ return f"{ins['ip']}:{ins['port']}"
39
+
40
+
41
+ def test_slot_stability():
42
+ """测试 1: user_id 到虚拟槽位的映射永久固定"""
43
+ print("=" * 60)
44
+ print("测试 1: user_id → 虚拟槽位 永久固定")
45
+ print("=" * 60)
46
+
47
+ users = [f"user_{i}" for i in range(100)]
48
+ slots_round1 = {u: user_to_slot(u) for u in users}
49
+ slots_round2 = {u: user_to_slot(u) for u in users}
50
+
51
+ assert slots_round1 == slots_round2, "槽位映射不稳定!"
52
+ print(f"✅ {len(users)} 个用户的槽位映射在两次调用中完全一致")
53
+
54
+ # 验证槽位范围
55
+ for u, s in slots_round1.items():
56
+ assert 0 <= s < VIRTUAL_SLOT_COUNT, f"槽位 {s} 超出范围"
57
+ print(f"✅ 所有槽位号在 [0, {VIRTUAL_SLOT_COUNT-1}] 范围内")
58
+
59
+ # 显示几个示例
60
+ print("\n示例映射:")
61
+ for u in users[:5]:
62
+ print(f" {u} → 槽位 {user_to_slot(u)}")
63
+ print()
64
+
65
+
66
+ def test_user_stickiness():
67
+ """测试 2: 同一用户在相同实例列表下始终路由到同一实例"""
68
+ print("=" * 60)
69
+ print("测试 2: 用户粘性 - 相同实例列表,路由不变")
70
+ print("=" * 60)
71
+
72
+ instances = make_instances(3)
73
+ users = [f"user_{i}" for i in range(200)]
74
+
75
+ results = []
76
+ for _ in range(5): # 调用 5 次
77
+ round_result = {u: instance_key(resolve_user_instance(u, instances)) for u in users}
78
+ results.append(round_result)
79
+
80
+ # 所有轮次的结果应该完全一致
81
+ for i in range(1, len(results)):
82
+ assert results[0] == results[i], f"第 {i} 轮结果不一致!"
83
+
84
+ print(f"✅ {len(users)} 个用户在 5 次调用中路由完全一致")
85
+
86
+ # 统计分布
87
+ counter = Counter(results[0].values())
88
+ print(f"\n分布: {dict(counter)}")
89
+ print()
90
+
91
+
92
+ def test_add_instance_minimal_migration():
93
+ """测试 3: 新增实例时最少迁移"""
94
+ print("=" * 60)
95
+ print("测试 3: 新增沙箱 - 只有部分用户迁移")
96
+ print("=" * 60)
97
+
98
+ users = [f"user_{i}" for i in range(1000)]
99
+
100
+ # 3 个实例
101
+ instances_3 = make_instances(3)
102
+ mapping_3 = {u: instance_key(resolve_user_instance(u, instances_3)) for u in users}
103
+
104
+ # 扩到 4 个实例
105
+ instances_4 = make_instances(4)
106
+ mapping_4 = {u: instance_key(resolve_user_instance(u, instances_4)) for u in users}
107
+
108
+ # 计算迁移数
109
+ migrated = sum(1 for u in users if mapping_3[u] != mapping_4[u])
110
+ migration_rate = migrated / len(users) * 100
111
+
112
+ print(f"3 → 4 实例: {migrated}/{len(users)} 用户迁移 ({migration_rate:.1f}%)")
113
+ print(f"理论最优迁移率: ~{100/4:.1f}% (1/4 的用户应被重新分配到新实例)")
114
+
115
+ # 迁移率应接近 1/N(新增第 4 个实例,理论 ~25% 的用户迁移)
116
+ assert migration_rate < 40, f"迁移率 {migration_rate:.1f}% 过高!"
117
+ print(f"✅ 迁移率 {migration_rate:.1f}% 在合理范围内")
118
+
119
+ # 验证未迁移的用户保持不变
120
+ unchanged = sum(1 for u in users if mapping_3[u] == mapping_4[u])
121
+ print(f"✅ {unchanged}/{len(users)} 用户保持原有沙箱不变")
122
+
123
+ # 扩到 5 个实例
124
+ instances_5 = make_instances(5)
125
+ mapping_5 = {u: instance_key(resolve_user_instance(u, instances_5)) for u in users}
126
+ migrated_4_to_5 = sum(1 for u in users if mapping_4[u] != mapping_5[u])
127
+ rate_4_to_5 = migrated_4_to_5 / len(users) * 100
128
+ print(f"4 → 5 实例: {migrated_4_to_5}/{len(users)} 用户迁移 ({rate_4_to_5:.1f}%)")
129
+ assert rate_4_to_5 < 30, f"迁移率 {rate_4_to_5:.1f}% 过高!"
130
+ print(f"✅ 迁移率 {rate_4_to_5:.1f}% 在合理范围内")
131
+ print()
132
+
133
+
134
+ def test_remove_instance():
135
+ """测试 4: 删除实例时受影响用户被分散"""
136
+ print("=" * 60)
137
+ print("测试 4: 删除沙箱 - 受影响用户被分散到其他实例")
138
+ print("=" * 60)
139
+
140
+ users = [f"user_{i}" for i in range(1000)]
141
+
142
+ # 4 个实例
143
+ instances_4 = make_instances(4)
144
+ mapping_4 = {u: instance_key(resolve_user_instance(u, instances_4)) for u in users}
145
+
146
+ # 减到 3 个(删除最后一个)
147
+ instances_3 = make_instances(3)
148
+ mapping_3 = {u: instance_key(resolve_user_instance(u, instances_3)) for u in users}
149
+
150
+ # 被删除实例的用户
151
+ removed_key = "10.0.0.4:8080"
152
+ affected_users = [u for u in users if mapping_4[u] == removed_key]
153
+ print(f"被删除实例 {removed_key} 上有 {len(affected_users)} 个用户")
154
+
155
+ # 这些用户在 3 实例下应该被分散到其余实例
156
+ if affected_users:
157
+ new_destinations = Counter(mapping_3[u] for u in affected_users)
158
+ print(f"迁移分布: {dict(new_destinations)}")
159
+
160
+ # 不应该有任何用户还路由到已删除的实例
161
+ for u in affected_users:
162
+ assert mapping_3[u] != removed_key, f"用户 {u} 仍路由到已删除实例!"
163
+ print(f"✅ 所有受影响用户已迁移到其他实例")
164
+
165
+ # 未受影响的用户保持不变
166
+ unaffected = [u for u in users if mapping_4[u] != removed_key]
167
+ unchanged = sum(1 for u in unaffected if mapping_4[u] == mapping_3[u])
168
+ print(f"✅ {unchanged}/{len(unaffected)} 未受影响用户保持原有沙箱")
169
+ print()
170
+
171
+
172
+ def test_uniform_distribution():
173
+ """测试 5: 虚拟槽位均匀分布到各实例"""
174
+ print("=" * 60)
175
+ print("测试 5: 均匀分布 - 虚拟槽位到实例的分配")
176
+ print("=" * 60)
177
+
178
+ for n_instances in [2, 3, 5, 10]:
179
+ instances = make_instances(n_instances)
180
+ ring = ConsistentHashMap()
181
+ ring.build(instances)
182
+
183
+ # 统计所有虚拟槽位的分配
184
+ counter = Counter()
185
+ for slot in range(VIRTUAL_SLOT_COUNT):
186
+ key = ring.get_instance(slot)
187
+ if key:
188
+ counter[key] += 1
189
+
190
+ total = sum(counter.values())
191
+ expected = total / n_instances
192
+ max_deviation = max(abs(v - expected) / expected * 100 for v in counter.values())
193
+
194
+ print(f" {n_instances} 个实例: 每实例期望 {expected:.0f} 槽位, "
195
+ f"最大偏差 {max_deviation:.1f}%")
196
+ print(f" 实际分布: {dict(counter)}")
197
+
198
+ # 偏差应小于 15%
199
+ assert max_deviation < 15, f"偏差 {max_deviation:.1f}% 过大!"
200
+ print(f" ✅ 偏差在合理范围内")
201
+ print()
202
+
203
+
204
+ def test_large_scale_user_distribution():
205
+ """测试 6: 大规模用户分布"""
206
+ print("=" * 60)
207
+ print("测试 6: 10000 用户在 5 个沙箱上的分布")
208
+ print("=" * 60)
209
+
210
+ instances = make_instances(5)
211
+ users = [f"user_{i:05d}" for i in range(10000)]
212
+
213
+ counter = Counter()
214
+ for u in users:
215
+ ins = resolve_user_instance(u, instances)
216
+ counter[instance_key(ins)] += 1
217
+
218
+ expected = len(users) / len(instances)
219
+ max_deviation = max(abs(v - expected) / expected * 100 for v in counter.values())
220
+
221
+ print(f"每实例期望: {expected:.0f} 用户")
222
+ print(f"实际分布: {dict(counter)}")
223
+ print(f"最大偏差: {max_deviation:.1f}%")
224
+ assert max_deviation < 15, f"偏差 {max_deviation:.1f}% 过大!"
225
+ print(f"✅ 10000 用户在 5 个沙箱上分布均匀")
226
+ print()
227
+
228
+
229
+ def test_comparison_with_old_method():
230
+ """测试 7: 对比新旧方法在增减实例时的迁移率"""
231
+ print("=" * 60)
232
+ print("测试 7: 新(一致性哈希) vs 旧(取模) 方法迁移率对比")
233
+ print("=" * 60)
234
+
235
+ users = [f"user_{i}" for i in range(1000)]
236
+
237
+ def old_method(user_id, instances):
238
+ hash_value = int(hashlib.md5(user_id.encode()).hexdigest(), 16)
239
+ idx = hash_value % len(instances)
240
+ return instance_key(instances[idx])
241
+
242
+ def new_method(user_id, instances):
243
+ return instance_key(resolve_user_instance(user_id, instances))
244
+
245
+ for old_n, new_n in [(3, 4), (4, 5), (5, 6), (10, 11)]:
246
+ old_ins = make_instances(old_n)
247
+ new_ins = make_instances(new_n)
248
+
249
+ # 旧方法迁移率
250
+ old_mapping_before = {u: old_method(u, old_ins) for u in users}
251
+ old_mapping_after = {u: old_method(u, new_ins) for u in users}
252
+ old_migrated = sum(1 for u in users if old_mapping_before[u] != old_mapping_after[u])
253
+
254
+ # 新方法迁移率
255
+ new_mapping_before = {u: new_method(u, old_ins) for u in users}
256
+ new_mapping_after = {u: new_method(u, new_ins) for u in users}
257
+ new_migrated = sum(1 for u in users if new_mapping_before[u] != new_mapping_after[u])
258
+
259
+ print(f" {old_n} → {new_n} 实例:")
260
+ print(f" 旧方法(取模): {old_migrated}/{len(users)} 迁移 ({old_migrated/len(users)*100:.1f}%)")
261
+ print(f" 新方法(一致性): {new_migrated}/{len(users)} 迁移 ({new_migrated/len(users)*100:.1f}%)")
262
+ print(f" 改善: {old_migrated - new_migrated} 个用户免于迁移")
263
+ print()
264
+
265
+
266
+ if __name__ == "__main__":
267
+ print("\n🚀 一致性哈希沙箱路由 - 验证测试\n")
268
+
269
+ test_slot_stability()
270
+ test_user_stickiness()
271
+ test_add_instance_minimal_migration()
272
+ test_remove_instance()
273
+ test_uniform_distribution()
274
+ test_large_scale_user_distribution()
275
+ test_comparison_with_old_method()
276
+
277
+ print("=" * 60)
278
+ print("🎉 所有测试通过!")
279
+ print("=" * 60)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.2.6a2
3
+ Version: 0.2.6a3
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -136,8 +136,9 @@ sycommon/agent/mcp/__init__.py,sha256=iKrdDhIrFsNIkqG_kgcwNe-nOiM6uVfolKv44LfQ-F
136
136
  sycommon/agent/mcp/models.py,sha256=zda4ho-UTYOJ5oPAZPZ-vxhfDRLghUCsMroHnA2A2QQ,1490
137
137
  sycommon/agent/mcp/tool_loader.py,sha256=OQa-lE7RL2s3Lqh-kNOKaUrb130hvorB_HjyFBqZBZs,10960
138
138
  sycommon/agent/sandbox/__init__.py,sha256=jR7LlkD4J4Y6QYyRXQClkwmqDBCCPmycV_hQV9p9YHw,4621
139
+ sycommon/agent/sandbox/consistent_hash.py,sha256=8Jgk-W4NAD2-u5_vKRVlPmql_e0Vy4aNORiXOuzvIrs,7277
139
140
  sycommon/agent/sandbox/file_ops.py,sha256=sqNxlrrzvgJPpHqJB98Us-jsKOYLjal4YUA2Ej7abeU,25852
140
- sycommon/agent/sandbox/http_sandbox_backend.py,sha256=pBCtNXbuaDn_p_UFaOPKTAGzjcRYhpogbxbiQoOMxG4,63255
141
+ sycommon/agent/sandbox/http_sandbox_backend.py,sha256=sLCbFAwlNRAODr4r4C6QIUxr1NelF50bBIWDE7VOe8w,63141
141
142
  sycommon/agent/sandbox/minio_sync.py,sha256=2vc0uw4IVER9F2W-dja1wbZwEy4G8UVTz4kDaBDzWBE,21919
142
143
  sycommon/agent/sandbox/sandbox_pool.py,sha256=eMn8sLakCWf90l6ni2-333QM8oBdX1CflV-WzneFp_k,9133
143
144
  sycommon/agent/sandbox/sandbox_recovery.py,sha256=X-eDODx1tmGMh_iTngV6e1ppfDBHpTdkPreJusN5MHY,7358
@@ -257,6 +258,7 @@ sycommon/synacos/nacos_service_discovery.py,sha256=LQkMAjpXmsK_STbW9aNk0hdwJJJtG
257
258
  sycommon/synacos/nacos_service_registration.py,sha256=dR2yn7I7yHbcXUxA-Lj9uy-Oqg6ARdY_Jp_cCS2Sp24,10745
258
259
  sycommon/synacos/param.py,sha256=KcfSkxnXOa0TGmCjY8hdzU9pzUsA8-4PeyBKWI2-568,1765
259
260
  sycommon/tests/deep_agent_server.py,sha256=t7snT2J6KWZrjJsYVi4TU5jpfvXNz4iuzMk4cc-kQGM,16696
261
+ sycommon/tests/test_consistent_hash.py,sha256=sOHM88LeQCqpXtSjFecv1ylKZ-PsTKAqFTm_V7pHvFQ,10015
260
262
  sycommon/tests/test_consumer_loss_rootcause.py,sha256=rP_XdbGbzOM9RB36vk0gU4kqipXG9iHqrCN8rKyd2Aw,16294
261
263
  sycommon/tests/test_consumer_recovery.py,sha256=pMMtOwHIijNTZxg3IVBYSGFYgezMX0PUeylks8xzMM8,10364
262
264
  sycommon/tests/test_deep_agent.py,sha256=34gP2KL8SSmtqqhaA9OV97OAl_I0T4TfEutZrR_0srM,5874
@@ -282,8 +284,8 @@ sycommon/tools/syemail.py,sha256=BDFhgf7WDOQeTcjxJEQdu0dQhnHFPO_p3eI0-Ni3LhQ,561
282
284
  sycommon/tools/timing.py,sha256=OiiE7P07lRoMzX9kzb8sZU9cDb0zNnqIlY5pWqHcnkY,2064
283
285
  sycommon/xxljob/__init__.py,sha256=7eoBlQxv-B39IfRSCY2bkqdGYs1QRe1umAWd88VMEEM,86
284
286
  sycommon/xxljob/xxljob_service.py,sha256=JIEJaGXhqrTLcyxlyynSrsHg9bBnDNzX-D4qIWLRPUE,6815
285
- sycommon_python_lib-0.2.6a2.dist-info/METADATA,sha256=Utz4dW5hwd9N97fayxjevOMQ-wweDQZ2H_YdlsJmKl4,7913
286
- sycommon_python_lib-0.2.6a2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
287
- sycommon_python_lib-0.2.6a2.dist-info/entry_points.txt,sha256=gsR4SssKxDWjRU8ggidzNcdMXDPRSKRS7UaGyNP84Qg,92
288
- sycommon_python_lib-0.2.6a2.dist-info/top_level.txt,sha256=RgphKrg7nJyZ7irJqbxFr-5H2LUYTvI7ivoWZH2hcD0,29
289
- sycommon_python_lib-0.2.6a2.dist-info/RECORD,,
287
+ sycommon_python_lib-0.2.6a3.dist-info/METADATA,sha256=gKIykJEMsoqXkDXIHU1w3Kq5YK99dJcUCE8WUTfG3-g,7913
288
+ sycommon_python_lib-0.2.6a3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
289
+ sycommon_python_lib-0.2.6a3.dist-info/entry_points.txt,sha256=gsR4SssKxDWjRU8ggidzNcdMXDPRSKRS7UaGyNP84Qg,92
290
+ sycommon_python_lib-0.2.6a3.dist-info/top_level.txt,sha256=RgphKrg7nJyZ7irJqbxFr-5H2LUYTvI7ivoWZH2hcD0,29
291
+ sycommon_python_lib-0.2.6a3.dist-info/RECORD,,