问题:收到了结果,工作进程就已经退出了吗?
第 12 讲用 Pool.starmap 收集返回值。本讲把这一层拆开:一个生产者把题目送进队列,一个工作进程生成答案,父进程收集结果。最后还要确认两者退出。随后换成外部命令,观察超时以后谁负责清理。
阅读顺序是:创建进程 → Queue 交接 → 完成消息 → 等待退出 → 超时升级清理。这里不启动模型、Ray 或网络服务;实验只创建自己的短命子进程。
1. 先创建执行边界,再启动工作
Slime 的 launch_server_process 为模型服务选择 spawn,创建 multiprocessing.Process,调用 start(),然后检查服务健康。在健康检查中,is_alive() 只能回答“进程是否还活着”,不能回答“HTTP 服务已经可用”。
源码入口:launch_server_process 的启动和健康检查交接
launch_server_processslime/backends/sglang_utils/sglang_engine.py · L49–L63
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
multiprocessing.set_start_method("spawn", force=True)
server_args.host = server_args.host.strip("[]")
p = multiprocessing.Process(target=_launch_server, args=(server_args, os.getpid()))
p.start()
if getattr(server_args, "node_rank", 0) != 0:
return p
_wait_server_healthy(
base_url=server_args.url(),
api_key=server_args.api_key,
is_process_alive=lambda: p.is_alive(),
)
return p教学程序使用同一种启动方式,但把模型服务换成生成固定答案的 worker_process:
ctx = multiprocessing.get_context("spawn")
task_queue, done_queue = ctx.Queue(maxsize=4), ctx.Queue(maxsize=4)
process = ctx.Process(
target=worker_process,
args=(task_queue, done_queue, query_single_turn, get_rule_based_math_reward, None, {}),
)
process.start()
Process(...) 先建立进程对象;start() 才安排子进程执行 target(*args)。spawn 会启动新解释器,因而把入口放在 if __name__ == "__main__": 下,worker 放在模块顶层,传递可序列化的参数。子进程不会共享父进程里的普通列表。
这里 multiprocessing.get_context("spawn") 返回具有 Process、Queue、Pool 工厂的上下文,相关对象统一从它创建。源码用 multiprocessing.set_start_method("spawn", force=True) 改全局选择;库式代码通常更适合局部上下文。不要在任意导入位置反复设置全局启动方式,也不要混用不同上下文创建的同步对象。Python 3.12 启动方式
创建者负责清理自己创建的进程。multiprocessing.active_children() 返回当前进程仍活着的 multiprocessing 子进程,不是全系统进程表。Slime 在启动前后取差集记录新路由器;实验只检查已经握有的 worker 是否在其中,不遍历并杀掉其他任务的子进程。
源码入口:ServingCluster 保存本轮启动的子进程
ServingCluster.__init__:新子进程归属slime/ray/serving.py · L57–L78
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
existing_children = {process.pid for process in multiprocessing.active_children()}
try:
# Constructor failure must release partial topology too. A detached
# actor can fail before any driver obtains a usable deployment.
self.placements = create_placement_groups(args)
try:
self.servers, handles = (
({}, []) if args.debug_train_only else start_rollout_servers(args, self.placements["rollout"])
)
finally:
self.router_processes = [
process for process in multiprocessing.active_children() if process.pid not in existing_children
]
ray.get(handles)
self.lock = Lock.options(num_cpus=0, num_gpus=0).remote()
except BaseException:
try:
self.dispose()
except Exception:
logging.getLogger(__name__).exception("Failed to dispose partial serving deployment")
raise
2. 从题目队列走到完成消息
输入交给 task_queue.put(item),worker 用 task_queue.get() 取出。计算结束后,把含 messages、reward、instance_id、extra_info 的结果交给 done_queue.put(output_item)。这是真实生成器使用的字段和交接方向。
def worker_process(task_queue, done_queue, rollout_func, reward_func, client, sampling_params):
for item in iter(task_queue.get, "STOP"):
messages, finish_reason = rollout_func(client, item["prompt"], sampling_params)
item["messages"] = messages
output_item = {
"messages": messages,
"reward": reward_func(item),
"instance_id": item["instance_id"],
"extra_info": {"finish_reason": finish_reason},
}
done_queue.put(output_item)
done_queue.put("COMPLETE")
这是教学裁剪:保留原 worker_process 的签名、两条 Queue 和 STOP/COMPLETE 协议;只启动一个 worker,固定返回答案 4,删去 HTTP 调用、UUID、采样统计、JSON 字符串输入和结果上报。extra_info 只保留一个字段,真实实现放入更多题目与采样信息。生产者 read_data_into_queue 从原来的局部函数移到模块顶层,显式接收队列,以便用 spawn 导入;真实实现还读文件、打乱数据并重复投递。
源码入口:worker_process 接收题目、生成结果、发送 COMPLETE
worker_processslime_plugins/rollout_buffer/generator/base_generator.py · L108–L140
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
def worker_process(task_queue, done_queue, rollout_func, reward_func, client, sampling_params):
for line in iter(task_queue.get, "STOP"):
if isinstance(line, str):
item = json.loads(line)
else:
item = line
# try:
messages, finish_reason = rollout_func(client, item["prompt"], sampling_params)
item["uid"] = str(uuid.uuid4())
item["messages"] = messages
reward = reward_func(item)
item["rollout_index"] = 1
item["reward"] = reward
item["extra_info"] = {}
item.update(sampling_params)
item["timestamp"] = str(time.time())
item["round_number"] = len([_ for _ in item["messages"] if _["role"] == "assistant"])
item["finish_reason"] = finish_reason
output_item = {
"uid": item.pop("uid"),
"messages": messages,
"reward": reward,
"instance_id": item.pop("instance_id"),
"extra_info": item,
}
done_queue.put(output_item)
done_queue.put("COMPLETE")源码入口:BaseGenerator.run 创建 Queue、投递 STOP、启动进程并消费结果
BaseGenerator.runslime_plugins/rollout_buffer/generator/base_generator.py · L192–L265
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
def run(self, input_file, rollout_func, reward_func):
task_queue, done_queue = Queue(maxsize=self.queue_size), Queue(maxsize=self.queue_size)
def read_data_into_queue():
cnt = 0
items = []
skipped_count = 0
with open(input_file) as f:
for i, line in enumerate(f):
item = json.loads(line)
if "instance_id" not in item:
item["instance_id"] = i
items.append(item)
random.shuffle(items)
for _ in range(self.num_repeats):
for item in items:
for _ in range(self.num_repeat_per_sample):
item_repeat = copy.deepcopy(item)
if "uid" not in item_repeat:
item_repeat["uid"] = str(uuid.uuid4())
# Check if instance_id should be skipped
if self.skip_instance_ids is not None and item_repeat["instance_id"] in self.skip_instance_ids:
print(f"Skipping instance_id: {item_repeat['instance_id']}")
# Remove from skip list to handle potential duplicates in multiple epochs
self.skip_instance_ids.remove(item_repeat["instance_id"])
skipped_count += 1
continue
task_queue.put(item_repeat)
cnt += 1
time.sleep(300)
if skipped_count > 0:
remaining_skip_count = len(self.skip_instance_ids) if self.skip_instance_ids is not None else 0
print(
f"Rollout summary: skipped {skipped_count} instance_ids, {remaining_skip_count} still in skip list"
)
for _ in range(self.num_process):
task_queue.put("STOP")
processes = []
SAMPLING_PARAMS["max_tokens"] = self.max_tokens
for _ in range(self.num_process):
process = Process(
target=partial(worker_process, client=self.client, sampling_params=SAMPLING_PARAMS),
args=(task_queue, done_queue, rollout_func, reward_func),
)
process.start()
processes.append(process)
process = Process(target=read_data_into_queue)
process.start()
progress_bar = tqdm()
num_finished = 0
while num_finished < self.num_process:
item = done_queue.get()
if item == "COMPLETE":
num_finished += 1
else:
assert "reward" in item, f"reward not in item: {item}"
assert "instance_id" in item, f"instance_id not in item: {item}"
self.send_data_to_buffer(item)
progress_bar.update(1)
progress_bar.close()
return "finished"每个 worker 需要自己的停止消息。一个 STOP 只能被一个消费者取走;父进程也要收齐 worker 数量的 COMPLETE,不能因为暂时没收到结果就判断整个生成任务完成。
multiprocessing.Queue.empty() 和 qsize() 只适合诊断,不能拿来决定“接下来 get() 一定不会阻塞”或“所有生产者已经结束”。put() 后还有后台线程把数据送进管道,观察值也会被其他进程立即改变;qsize() 在 macOS 等平台还可能不可用。实验用明确的结束消息,父进程用 done_queue.get(timeout=10) 给异常路径留出口,超时抛出标准库 queue.Empty。Queue 的传输与限制
3. COMPLETE 以后,还要 join
运行 example.py:
python3 -W error example.py
Python 3.12 输出:
owned worker True
results [(10, 1), (11, 1)]
joined True
join timed out True
process reaped True
pool [4, 9]
前半段先消费结果,再对生产者和 worker 分别 join(timeout=5)。收到 COMPLETE 是业务协议;join 等待的是操作系统进程结束。join(timeout=...) 的返回值不表示成功与否,要再看 is_alive() 和 exitcode。退出后的 Process.close() 释放进程对象的句柄,不能用它代替停止仍活着的进程。
后半段刻意创建一个睡眠 60 秒的进程,join(timeout=0.01) 返回时它仍活着,随后按照 Slime 路由器的顺序处理:
if process.is_alive():
process.terminate()
process.join(timeout=5)
if process.is_alive():
process.kill()
process.join()
terminate() 在 POSIX 发送 SIGTERM,kill() 在 POSIX 使用 SIGKILL;Windows 的行为不同,不能把它们都理解成可执行 Python 清理逻辑的通知。它们也不自动终止后代进程。源码把等待限制在剩余清理预算内;教学例子只处理自己的单个无后代睡眠进程,因此最后的 join() 不另设置预算。Process 生命周期
源码入口:ServingCluster.dispose 对路由器 terminate → join → kill → join
ServingCluster.dispose:路由器回收slime/ray/serving.py · L317–L323
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
for router in self.router_processes:
if router.is_alive():
cleanup.run("terminate router", router.terminate)
cleanup.run("join router", router.join, timeout=min(10, cleanup.remaining / 2))
if router.is_alive():
cleanup.run("kill router", router.kill)
cleanup.run("reap router", router.join, timeout=cleanup.remaining)强制终止正在写 Queue 或持锁的进程,可能留下损坏的管道或让其他进程永久等锁。正常路径优先 STOP;异常路径停止本轮进程后,放弃本轮通信对象。实验最后对两条 Queue 调用 close()、join_thread(),释放本进程的队列资源并等待发送线程完成。不要在消费者还没取走大量结果时先 join 生产者,否则生产者可能卡在刷新队列,父进程又卡在等它退出。进程与 Queue 的清理注意事项
4. Pool 替你分派任务,仍需要关闭边界
现在回到第 12 讲:ctx.Pool(...) 管理多个 worker,pool.starmap(conversion_worker, ...) 分派参数元组并等待返回值。本讲例子把生命周期显式写出:成功后 pool.close() 停止接收新任务;随后 pool.join() 等待 worker 退出;失败时改为 pool.terminate() 放弃尚未完成的工作,再 join()。
真实转换工具使用 with ctx.Pool(num_workers) as pool:。要注意,Pool 的上下文退出调用 terminate();不是所有上下文管理器都承诺等完未完成工作。此处 starmap 已经同步返回,结果才进入后续权重汇总。如果改成后台提交后立刻离开 with,就不能沿用“所有任务都做完了”的推断。Pool 关闭合同
源码入口:转换工具在 Pool 内收集结果,退出后汇总
转换进程池tools/convert_torch_dist_to_hf_parallel.py · L511–L528
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
ctx = multiprocessing.get_context("spawn")
with ctx.Pool(num_workers) as pool:
results = pool.starmap(
conversion_worker,
[
(
i,
key_chunks[i],
args,
megatron_args,
args.model_name,
state_dict_metadata_dict,
load_max_workers,
save_max_workers,
)
for i in range(num_workers)
],
)5. 外部命令超时:异常发生了,进程还活着
Process 执行 Python callable;subprocess.Popen 启动命令并立即交回控制对象。Slime 的 SubprocessInterpreter.run_file 用 Popen 跑文件,再用 communicate(timeout=...) 收集输出并等待退出。超过期限会抛 subprocess.TimeoutExpired,但这一调用不会自动杀进程。
try:
stdout, stderr = proc.communicate(timeout=0.05)
except subprocess.TimeoutExpired:
print("timeout still running", proc.poll() is None)
proc.kill()
stdout, stderr = proc.communicate()
第二次 communicate() 才完成输出读取、管道收尾和退出等待。不能只记一条“超时”日志就丢掉 proc。这是本例直接保留的源码顺序;命令替换成当前 Python 解释器的 time.sleep(60),没有调用真实模型或工具服务。communicate 超时合同
源码入口:SubprocessInterpreter.run_file 的超时回收
SubprocessInterpreter.run_file:超时回收examples/strands_sglang/subprocess_interpreter.py · L227–L244
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
try:
proc = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
env=env,
shell=False, # Never use shell=True for security
)
# Add timeout to prevent hanging processes
stdout, stderr = proc.communicate(timeout=self.execution_timeout)
return_code = proc.returncode
except subprocess.TimeoutExpired:
proc.kill()
stdout, stderr = proc.communicate()
return_code = proc.returncode
timeout_msg = f"Process timed out after {self.execution_timeout} seconds and was terminated."
stderr = f"{stderr}\n{timeout_msg}"proc.poll() 不阻塞:仍运行返回 None,结束返回退出码。proc.wait(timeout=...) 等待退出但不读取输出。给 stdout/stderr 配了 PIPE 却一直只 wait,孩子可能写满管道后等父进程读取;父进程同时等孩子退出。本例因此对捕获输出的进程使用 communicate,对丢弃输出的进程才直接 terminate()、wait(),超时再 kill()、wait()。Popen 等待与终止
源码入口:GPU 命令包装器检查运行状态并等待退出
_run_command:检查与等待子进程tests/ci/gpu_lock_exec.py · L38–L65
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
def _run_command(args):
cmd = args.cmd
if cmd[0] == "--":
cmd = cmd[1:]
child = subprocess.Popen(cmd, start_new_session=True)
def terminate_child(signum, _frame):
if child.poll() is None:
child.terminate()
try:
child.wait(timeout=5)
sys.exit(128 + signum)
except subprocess.TimeoutExpired:
pass
try:
os.killpg(child.pid, signum)
except ProcessLookupError:
pass
try:
child.wait(timeout=5)
except subprocess.TimeoutExpired:
try:
os.killpg(child.pid, signal.SIGKILL)
except ProcessLookupError:
pass
child.wait()6. 输出送到哪里,决定父进程怎样等待
运行 subprocess_example.py,依次观察合并输出、超时回收、停止睡眠进程和检查命令结果:
merged ['out', 'err']
timeout still running True
killed and reaped True
terminated and waited True
command failed 7
check_call 0
check_output probe
subprocess.PIPE 建立父子进程间的管道;stderr=subprocess.STDOUT 把错误输出合并进标准输出,此时 communicate() 返回的第二项为 None;subprocess.DEVNULL 丢弃相应输出。示例用 -u 消除子 Python 的输出缓冲影响,先写 out 再写 err。subprocess 输出参数
Slime 的流式训练测试用 STDOUT 合并日志,边读 proc.stdout 边显示,再 wait();启动训练前探测 ray status 则把输出丢到 DEVNULL,只检查状态码。两者都是同步代码,管道存在不表示正在使用 asyncio。
源码入口:合并训练日志,读完再 wait
_run:合并日志并等待tests/test_glm52_6layer_deterministic_e2e.py · L402–L409
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
proc = subprocess.Popen(cmd, env=env, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
lines = []
for line in proc.stdout:
lines.append(line)
sys.stdout.write(line)
sys.stdout.flush()
proc.wait()
return proc.returncode, "".join(lines)源码入口:只关心 ray status 的返回码
execute_train:探测 Ray 状态slime/utils/external_utils/command_utils.py · L111–L121
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
# Successful training cleans up its own resources; failures retain them.
if not external_ray:
# A manual trainer restart must leave the Ray head and its detached
# rollout session alive. A stale address file alone is not proof of a
# running cluster, so query the head before deciding to start one.
ray_running = (
subprocess.run(
["ray", "status"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=30
).returncode
== 0
)7. 命令失败和命令超时是两条分支
Slime 的 exec_command 用 subprocess.run(..., check=True),非零退出时收到 subprocess.CalledProcessError;异常携带 returncode,捕获了输出时还可检查 stdout/stderr。check_call() 同样在非零退出时抛异常,成功返回 0;check_output() 成功返回捕获的 stdout。这些接口已完成等待,返回值合同不同于立即返回控制对象的 Popen。检查退出状态
源码入口:exec_command 保留命令失败的退出码与输出
exec_commandslime/utils/misc.py · L79–L97
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
def exec_command(cmd: str, capture_output: bool = False) -> str | None:
print(f"EXEC: {cmd}", flush=True)
try:
result = subprocess.run(
["bash", "-c", cmd],
shell=False,
check=True,
capture_output=capture_output,
**(dict(text=True) if capture_output else {}),
)
except subprocess.CalledProcessError as e:
if capture_output:
print(f"{e.stdout=} {e.stderr=}")
raise
if capture_output:
print(f"Captured stdout={result.stdout} stderr={result.stderr}")
return result.stdout源码入口:check_call 检查命令是否存在
SubprocessInterpreter._is_command_availableexamples/strands_sglang/subprocess_interpreter.py · L425–L437
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
else: # Unix-like systems
# On Unix-like systems, use which to find the command
try:
with open(os.devnull, "w") as devnull:
subprocess.check_call(
["which", command],
stdout=devnull,
stderr=devnull,
shell=False,
)
return True
except subprocess.CalledProcessError:
return False源码入口:check_output 获取网卡查询结果
_get_bond_ipv4:check_outputtests/test_qwen3_4B_external_pd.py · L45–L56
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
if not net_root.exists():
return None
bond_ifaces = [
path.name for path in net_root.iterdir() if path.name.startswith("bond") and path.name[4:].isdigit()
]
bond_ifaces.sort(key=lambda name: int(name[4:]))
for iface in bond_ifaces:
try:
output = subprocess.check_output(["ip", "-o", "-4", "addr", "show", "dev", iface], text=True)
except (OSError, subprocess.CalledProcessError):
continueTimeoutExpired 表示等待期限耗尽;CalledProcessError 表示受检查的命令已经以非零状态结束。普通 Popen 收到非零退出码不会自动抛后者。run(timeout=...) 超时会先杀死并等待直接子进程,再重新抛出异常;Popen.communicate(timeout=...) 则把后续清理留给调用方,不能仅凭参数都叫 timeout 就套用同一段代码。run 的超时合同
《流畅的 Python》核对:相似接口与不同执行边界
书上怎么说:Process 的基本用法与 Thread 相似。
我的判断(本书未覆盖):本讲的 Slime 完成协议和失败清理顺序来自上述固定源码;Python 3.12 的具体方法合同以链接中的官方文档为准,不能从接口相似推导出共享内存或相同取消行为。
依据:第 19 章,spinner-with-processes。英文 corpus/en/ch19.md:245–294;中文 corpus/zh/ch19.md:233–282。书籍为第 2 版、基于 Python 3.10;示例验证环境为 Python 3.12.3。
原文:“Basic usage of the Process class is similar to Thread.”
对照:“❹ Process 类的基本用法与 Thread 相似。”
复述与小实验
先说明结果消息、进程退出、资源释放分别由哪个动作确认,再动手:把生产者的 STOP 暂时去掉,观察父进程怎样从带期限的 get() 进入清理;随后恢复。不要用 empty() 替代结束协议。
第二个问题:若一个服务进程又启动了其他进程,为什么对直接孩子调用一次 kill() 还不能证明服务已全部退出?下一讲沿这个问题进入进程组与信号。
展开本节完整可运行实验
"""Teaching cut: STOP/COMPLETE protocol, then process and Pool cleanup."""
import multiprocessing
import time
def query_single_turn(client, messages, sampling_params):
return messages + [{"role": "assistant", "content": "4"}], "stop"
def get_rule_based_math_reward(item):
return int(item["messages"][-1]["content"] == item["label"])
def worker_process(task_queue, done_queue, rollout_func, reward_func, client, sampling_params):
for item in iter(task_queue.get, "STOP"):
messages, finish_reason = rollout_func(client, item["prompt"], sampling_params)
item["messages"] = messages
output_item = {
"messages": messages,
"reward": reward_func(item),
"instance_id": item["instance_id"],
"extra_info": {"finish_reason": finish_reason},
}
done_queue.put(output_item)
done_queue.put("COMPLETE")
def read_data_into_queue(task_queue):
for instance_id in (10, 11):
task_queue.put({"instance_id": instance_id, "prompt": [], "label": "4"})
task_queue.put("STOP")
def conversion_worker(value):
return value * value
def main():
ctx = multiprocessing.get_context("spawn")
task_queue, done_queue = ctx.Queue(maxsize=4), ctx.Queue(maxsize=4)
processes = []
try:
process = ctx.Process(
target=worker_process,
args=(task_queue, done_queue, query_single_turn, get_rule_based_math_reward, None, {}),
)
process.start()
processes.append(process)
print("owned worker", process.pid in {child.pid for child in multiprocessing.active_children()})
producer = ctx.Process(target=read_data_into_queue, args=(task_queue,))
producer.start()
processes.append(producer)
results = []
while True:
item = done_queue.get(timeout=10)
if item == "COMPLETE":
break
results.append((item["instance_id"], item["reward"]))
print("results", sorted(results))
for process in processes:
process.join(timeout=5)
if process.is_alive() or process.exitcode != 0:
raise RuntimeError("worker did not exit successfully")
print("joined", all(not process.is_alive() for process in processes))
finally:
for process in processes:
if process.is_alive():
process.terminate()
process.join(timeout=5)
if process.is_alive():
process.kill()
process.join()
process.close()
for queue in (task_queue, done_queue):
queue.close()
queue.join_thread()
process = ctx.Process(target=time.sleep, args=(60,))
process.start()
try:
process.join(timeout=0.01)
print("join timed out", process.is_alive())
finally:
if process.is_alive():
process.terminate()
process.join(timeout=5)
if process.is_alive():
process.kill()
process.join()
print("process reaped", not process.is_alive())
process.close()
pool = ctx.Pool(2)
try:
print("pool", pool.starmap(conversion_worker, [(2,), (3,)]))
pool.close()
except BaseException:
pool.terminate()
raise
finally:
pool.join()
if __name__ == "__main__":
main()
本节对应的全部源码位置
下面把同类用法在 Slime 中的每个扫描位置接回本节讲解;点击文件名可查看完整源码快照。重复出现的 API 共用解释,具体输入与清理由所在函数决定。
展开本节 113 条源码记录
| 记录 / 讲解 | 源码位置 | 原代码 |
|---|---|---|
| occ-000205 | examples/retool/tool_sandbox.py:14 <module> | import subprocess |
| occ-000210 | examples/retool/tool_sandbox.py:267 PythonSandbox.execute_code | process = subprocess.Popen( |
| occ-000211 | examples/retool/tool_sandbox.py:269 PythonSandbox.execute_code | stdout=subprocess.PIPE, |
| occ-000212 | examples/retool/tool_sandbox.py:270 PythonSandbox.execute_code | stderr=subprocess.PIPE, |
| occ-000213 有接收者线索 | examples/retool/tool_sandbox.py:278 PythonSandbox.execute_code | stdout, stderr = process.communicate(timeout=self.timeout) |
| occ-000214 | examples/retool/tool_sandbox.py:285 PythonSandbox.execute_code | except subprocess.TimeoutExpired: |
| occ-000215 有接收者线索 | examples/retool/tool_sandbox.py:286 PythonSandbox.execute_code | process.kill() |
| occ-000274 | examples/strands_sglang/subprocess_interpreter.py:27 <module> | import subprocess |
| occ-000276 | examples/strands_sglang/subprocess_interpreter.py:228 SubprocessInterpreter.run_file | proc = subprocess.Popen( |
| occ-000277 | examples/strands_sglang/subprocess_interpreter.py:230 SubprocessInterpreter.run_file | stdout=subprocess.PIPE, |
| occ-000278 | examples/strands_sglang/subprocess_interpreter.py:231 SubprocessInterpreter.run_file | stderr=subprocess.PIPE, |
| occ-000279 有接收者线索 | examples/strands_sglang/subprocess_interpreter.py:237 SubprocessInterpreter.run_file | stdout, stderr = proc.communicate(timeout=self.execution_timeout) |
| occ-000280 | examples/strands_sglang/subprocess_interpreter.py:239 SubprocessInterpreter.run_file | except subprocess.TimeoutExpired: |
| occ-000281 有接收者线索 | examples/strands_sglang/subprocess_interpreter.py:240 SubprocessInterpreter.run_file | proc.kill() |
| occ-000282 有接收者线索 | examples/strands_sglang/subprocess_interpreter.py:241 SubprocessInterpreter.run_file | stdout, stderr = proc.communicate() |
| occ-000285 | examples/strands_sglang/subprocess_interpreter.py:416 SubprocessInterpreter._is_command_available | subprocess.check_call( |
| occ-000286 | examples/strands_sglang/subprocess_interpreter.py:423 SubprocessInterpreter._is_command_available | except subprocess.CalledProcessError: |
| occ-000287 | examples/strands_sglang/subprocess_interpreter.py:429 SubprocessInterpreter._is_command_available | subprocess.check_call( |
| occ-000288 | examples/strands_sglang/subprocess_interpreter.py:436 SubprocessInterpreter._is_command_available | except subprocess.CalledProcessError: |
| occ-000289 | examples/strands_sglang/subprocess_interpreter.py:467 SubprocessInterpreter.execute_command | proc = subprocess.Popen( |
| occ-000290 | examples/strands_sglang/subprocess_interpreter.py:469 SubprocessInterpreter.execute_command | stdout=subprocess.PIPE, |
| occ-000291 | examples/strands_sglang/subprocess_interpreter.py:470 SubprocessInterpreter.execute_command | stderr=subprocess.PIPE, |
| occ-000292 有接收者线索 | examples/strands_sglang/subprocess_interpreter.py:476 SubprocessInterpreter.execute_command | stdout, stderr = proc.communicate(timeout=self.execution_timeout) |
| occ-000662 | slime/backends/sglang_utils/deployment.py:4 <module> | import multiprocessing |
| occ-000664 | slime/backends/sglang_utils/deployment.py:53 _start_router | process = multiprocessing.Process( |
| occ-000665 有接收者线索 | slime/backends/sglang_utils/deployment.py:58 _start_router | process.start() |
| occ-000667 有接收者线索 | slime/backends/sglang_utils/deployment.py:60 _start_router | assert process.is_alive() |
| occ-000710 | slime/backends/sglang_utils/sglang_engine.py:4 <module> | import multiprocessing |
| occ-000713 | slime/backends/sglang_utils/sglang_engine.py:32 <module> | def launch_server_process(server_args: ServerArgs) -> multiprocessing.Process: |
| occ-000714 | slime/backends/sglang_utils/sglang_engine.py:49 launch_server_process | multiprocessing.set_start_method("spawn", force=True) |
| occ-000715 | slime/backends/sglang_utils/sglang_engine.py:51 launch_server_process | p = multiprocessing.Process(target=_launch_server, args=(server_args, os.getpid())) |
| occ-000717 有接收者线索 | slime/backends/sglang_utils/sglang_engine.py:52 launch_server_process | p.start() |
| occ-000718 有接收者线索 | slime/backends/sglang_utils/sglang_engine.py:60 launch_server_process.<lambda> | is_process_alive=lambda: p.is_alive(), |
| occ-000901 | slime/ray/serving.py:10 <module> | import multiprocessing |
| occ-000902 | slime/ray/serving.py:57 ServingCluster.__init__ | existing_children = {process.pid for process in multiprocessing.active_children()} |
| occ-000903 | slime/ray/serving.py:68 ServingCluster.__init__ | process for process in multiprocessing.active_children() if process.pid not in existing_children |
| occ-001091 | slime/rollout/rm_hub/ifbench.py:6 <module> | import subprocess |
| occ-001092 | slime/rollout/rm_hub/ifbench.py:27 _ensure_ifbench_repo | subprocess.run(clone_cmd, check=True, capture_output=True) |
| occ-001093 | slime/rollout/rm_hub/ifbench.py:62 _ensure_ifbench_dependencies | subprocess.run(install_cmd, check=True) |
| occ-001214 | slime/utils/external_utils/command_utils.py:9 <module> | import subprocess |
| occ-001215 | slime/utils/external_utils/command_utils.py:117 execute_train | subprocess.run( |
| occ-001216 | slime/utils/external_utils/command_utils.py:118 execute_train | ["ray", "status"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=30 |
| occ-001217 | slime/utils/external_utils/command_utils.py:118 execute_train | ["ray", "status"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=30 |
| occ-001272 | slime/utils/misc.py:2 <module> | import subprocess |
| occ-001273 | slime/utils/misc.py:83 exec_command | result = subprocess.run( |
| occ-001274 | slime/utils/misc.py:90 exec_command | except subprocess.CalledProcessError as e: |
| occ-001329 | slime_plugins/rollout_buffer/generator/base_generator.py:7 <module> | from multiprocessing import Process, Queue |
| occ-001330 | slime_plugins/rollout_buffer/generator/base_generator.py:7 <module> | from multiprocessing import Process, Queue |
| occ-001335 | slime_plugins/rollout_buffer/generator/base_generator.py:193 BaseGenerator.run | task_queue, done_queue = Queue(maxsize=self.queue_size), Queue(maxsize=self.queue_size) |
| occ-001336 | slime_plugins/rollout_buffer/generator/base_generator.py:193 BaseGenerator.run | task_queue, done_queue = Queue(maxsize=self.queue_size), Queue(maxsize=self.queue_size) |
| occ-001340 | slime_plugins/rollout_buffer/generator/base_generator.py:241 BaseGenerator.run | process = Process( |
| occ-001341 有接收者线索 | slime_plugins/rollout_buffer/generator/base_generator.py:245 BaseGenerator.run | process.start() |
| occ-001342 | slime_plugins/rollout_buffer/generator/base_generator.py:248 BaseGenerator.run | process = Process(target=read_data_into_queue) |
| occ-001343 有接收者线索 | slime_plugins/rollout_buffer/generator/base_generator.py:249 BaseGenerator.run | process.start() |
| occ-001364 | tests/ci/gpu_lock_exec.py:7 <module> | import subprocess |
| occ-001368 | tests/ci/gpu_lock_exec.py:43 _run_command | child = subprocess.Popen(cmd, start_new_session=True) |
| occ-001369 有接收者线索 | tests/ci/gpu_lock_exec.py:46 _run_command.terminate_child | if child.poll() is None: |
| occ-001370 有接收者线索 | tests/ci/gpu_lock_exec.py:47 _run_command.terminate_child | child.terminate() |
| occ-001371 有接收者线索 | tests/ci/gpu_lock_exec.py:49 _run_command.terminate_child | child.wait(timeout=5) |
| occ-001372 | tests/ci/gpu_lock_exec.py:51 _run_command.terminate_child | except subprocess.TimeoutExpired: |
| occ-001374 有接收者线索 | tests/ci/gpu_lock_exec.py:59 _run_command.terminate_child | child.wait(timeout=5) |
| occ-001375 | tests/ci/gpu_lock_exec.py:60 _run_command.terminate_child | except subprocess.TimeoutExpired: |
| occ-001378 有接收者线索 | tests/ci/gpu_lock_exec.py:65 _run_command.terminate_child | child.wait() |
| occ-001384 有接收者线索 | tests/ci/gpu_lock_exec.py:75 _run_command | returncode = child.poll() |
| occ-001847 | tests/test_glm52_6layer_deterministic_e2e.py:31 <module> | import subprocess |
| occ-001848 | tests/test_glm52_6layer_deterministic_e2e.py:72 _iface_ipv4 | out = subprocess.run(["ip", "-o", "-4", "addr", "show", ifname], capture_output=True, text=True).stdout.split() |
| occ-001849 | tests/test_glm52_6layer_deterministic_e2e.py:124 _skip_reason | visible = subprocess.run( |
| occ-001850 | tests/test_glm52_6layer_deterministic_e2e.py:132 _skip_reason | probe = subprocess.run([sys.executable, "-c", _PREREQ_PROBE], capture_output=True, text=True, env=probe_env) |
| occ-001851 | tests/test_glm52_6layer_deterministic_e2e.py:153 _download_ci_asset | subprocess.run(command, check=True) |
| occ-001853 | tests/test_glm52_6layer_deterministic_e2e.py:398 _run | r = subprocess.run(cmd, env=env, cwd=cwd, capture_output=True, text=True) |
| occ-001854 | tests/test_glm52_6layer_deterministic_e2e.py:402 _run | proc = subprocess.Popen(cmd, env=env, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) |
| occ-001855 | tests/test_glm52_6layer_deterministic_e2e.py:402 _run | proc = subprocess.Popen(cmd, env=env, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) |
| occ-001856 | tests/test_glm52_6layer_deterministic_e2e.py:402 _run | proc = subprocess.Popen(cmd, env=env, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) |
| occ-001857 有接收者线索 | tests/test_glm52_6layer_deterministic_e2e.py:408 _run | proc.wait() |
| occ-001859 | tests/test_optional_straw.py:4 <module> | import subprocess |
| occ-001861 | tests/test_optional_straw.py:144 test_default_rollout_and_training_work_without_straw | subprocess.run( |
| occ-001872 | tests/test_qwen2.5_0.5B_opd_sglang.py:2 <module> | import subprocess |
| occ-001875 | tests/test_qwen2.5_0.5B_opd_sglang.py:39 _launch_teacher_server | process = subprocess.Popen( |
| occ-001876 | tests/test_qwen2.5_0.5B_opd_sglang.py:57 _launch_teacher_server | stderr=subprocess.STDOUT, |
| occ-001877 有接收者线索 | tests/test_qwen2.5_0.5B_opd_sglang.py:64 _launch_teacher_server | if process.poll() is not None: |
| occ-001879 有接收者线索 | tests/test_qwen2.5_0.5B_opd_sglang.py:75 _launch_teacher_server | process.kill() |
| occ-001883 | tests/test_qwen2.5_0.5B_rollout_health.py:7 <module> | import subprocess |
| occ-001884 | tests/test_qwen2.5_0.5B_rollout_health.py:82 execute | subprocess.run(["ray", "stop", "--force"], check=True) |
| occ-001886 | tests/test_qwen2.5_0.5B_training_recovery.py:7 <module> | import subprocess |
| occ-001889 | tests/test_qwen2.5_0.5B_training_recovery.py:169 execute | subprocess.run( |
| occ-001895 | tests/test_qwen2.5_0.5B_training_recovery.py:374 execute | subprocess.run(["ray", "stop", "--force"], check=True) |
| occ-001896 | tests/test_qwen3_4B_external_pd.py:21 <module> | import subprocess |
| occ-001898 | tests/test_qwen3_4B_external_pd.py:54 _get_bond_ipv4 | output = subprocess.check_output(["ip", "-o", "-4", "addr", "show", "dev", iface], text=True) |
| occ-001899 | tests/test_qwen3_4B_external_pd.py:55 _get_bond_ipv4 | except (OSError, subprocess.CalledProcessError): |
| occ-001902 | tests/test_qwen3_4B_external_pd.py:157 <module> | ) -> subprocess.Popen: |
| occ-001904 | tests/test_qwen3_4B_external_pd.py:190 _launch_sglang_server | process = subprocess.Popen(cmd, env=env, stdout=log_file, stderr=subprocess.STDOUT) |
| occ-001905 | tests/test_qwen3_4B_external_pd.py:190 _launch_sglang_server | process = subprocess.Popen(cmd, env=env, stdout=log_file, stderr=subprocess.STDOUT) |
| occ-001906 有接收者线索 | tests/test_qwen3_4B_external_pd.py:201 _launch_sglang_server | if process.poll() is not None: |
| occ-001908 有接收者线索 | tests/test_qwen3_4B_external_pd.py:212 _launch_sglang_server | process.kill() |
| occ-001909 | tests/test_qwen3_4B_external_pd.py:222 execute | processes: list[subprocess.Popen] = [] |
| occ-001948 | tests/test_rollout_straw_transport.py:3 <module> | import subprocess |
| occ-001949 | tests/test_rollout_straw_transport.py:102 test_cpu_rollout_imports_do_not_require_sglang | subprocess.run( |
| occ-001950 | tests/test_rollout_straw_transport.py:261 test_payload_reference_is_small_and_readable_by_independent_process | subprocess.run( |
| occ-001972 | tests/test_rollout_straw_transport.py:1418 test_checkpoint_lock_released_when_controller_process_is_killed | process = subprocess.Popen( |
| occ-001973 | tests/test_rollout_straw_transport.py:1432 test_checkpoint_lock_released_when_controller_process_is_killed | stdin=subprocess.PIPE, |
| occ-001974 | tests/test_rollout_straw_transport.py:1433 test_checkpoint_lock_released_when_controller_process_is_killed | stdout=subprocess.PIPE, |
| occ-001975 | tests/test_rollout_straw_transport.py:1434 test_checkpoint_lock_released_when_controller_process_is_killed | stderr=subprocess.PIPE, |
| occ-001978 有接收者线索 | tests/test_rollout_straw_transport.py:1446 test_checkpoint_lock_released_when_controller_process_is_killed | process.kill() |
| occ-001979 有接收者线索 | tests/test_rollout_straw_transport.py:1447 test_checkpoint_lock_released_when_controller_process_is_killed | process.wait(timeout=30) |
| occ-002059 | tests/test_straw_fully_async_recovery.py:13 <module> | import subprocess |
| occ-002085 | tests/test_straw_fully_async_recovery.py:257 test_fully_async_sigkill_and_restart_preserves_results_and_prefixes | process = subprocess.Popen( |
| occ-002086 | tests/test_straw_fully_async_recovery.py:261 test_fully_async_sigkill_and_restart_preserves_results_and_prefixes | stderr=subprocess.STDOUT, |
| occ-002087 有接收者线索 | tests/test_straw_fully_async_recovery.py:268 test_fully_async_sigkill_and_restart_preserves_results_and_prefixes | assert process.poll() is None, log_path.read_text()[-12000:] |
| occ-002090 有接收者线索 | tests/test_straw_fully_async_recovery.py:278 test_fully_async_sigkill_and_restart_preserves_results_and_prefixes | assert process.wait(timeout=180) == 0, log_path.read_text()[-12000:] |
| occ-002229 | tests/test_training_recovery.py:291 test_recovery_e2e_contacts_dashboard_without_proxy | import subprocess |
| occ-002232 | tests/test_training_recovery.py:326 test_recovery_e2e_contacts_dashboard_without_proxy | monkeypatch.setattr(subprocess, "run", lambda *a, **kw: started.append(dict(os.environ))) |
| occ-002261 | tools/convert_torch_dist_to_hf_parallel.py:3 <module> | import multiprocessing |
| occ-002281 | tools/convert_torch_dist_to_hf_parallel.py:511 <module> | ctx = multiprocessing.get_context("spawn") |
专题首页 · 上一节 · 下一节:19 · 信号、进程组与文件锁:退出以后谁释放资源? · 源码与教程对照 · 全部用法