PKIPC 2.0.0__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.
- pkipc-2.0.0/PKG-INFO +184 -0
- pkipc-2.0.0/PKIPC.egg-info/PKG-INFO +184 -0
- pkipc-2.0.0/PKIPC.egg-info/SOURCES.txt +19 -0
- pkipc-2.0.0/PKIPC.egg-info/dependency_links.txt +1 -0
- pkipc-2.0.0/PKIPC.egg-info/requires.txt +10 -0
- pkipc-2.0.0/PKIPC.egg-info/top_level.txt +1 -0
- pkipc-2.0.0/README.md +168 -0
- pkipc-2.0.0/pkipc/__init__.py +12 -0
- pkipc-2.0.0/pkipc/_grpc_common.py +12 -0
- pkipc-2.0.0/pkipc/_grpc_host.py +153 -0
- pkipc-2.0.0/pkipc/_runtime.py +314 -0
- pkipc-2.0.0/pkipc/_stream.py +53 -0
- pkipc-2.0.0/pkipc/client.py +253 -0
- pkipc-2.0.0/pkipc/proto/__init__.py +1 -0
- pkipc-2.0.0/pkipc/proto/pkipc.proto +11 -0
- pkipc-2.0.0/pkipc/proto/pkipc_pb2.py +38 -0
- pkipc-2.0.0/pkipc/proto/pkipc_pb2.pyi +11 -0
- pkipc-2.0.0/pkipc/proto/pkipc_pb2_grpc.py +97 -0
- pkipc-2.0.0/pkipc/server.py +151 -0
- pkipc-2.0.0/pyproject.toml +43 -0
- pkipc-2.0.0/setup.cfg +4 -0
pkipc-2.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PKIPC
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Event runtime over gRPC for parent-child process communication
|
|
5
|
+
Author: PKIPC
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: grpcio<2,>=1.81.1
|
|
9
|
+
Requires-Dist: protobuf<7,>=6.33.5
|
|
10
|
+
Provides-Extra: test
|
|
11
|
+
Requires-Dist: pytest>=8.2; extra == "test"
|
|
12
|
+
Requires-Dist: ruff>=0.12; extra == "test"
|
|
13
|
+
Provides-Extra: build
|
|
14
|
+
Requires-Dist: grpcio-tools<2,>=1.81.1; extra == "build"
|
|
15
|
+
Requires-Dist: pyinstaller>=6.0; extra == "build"
|
|
16
|
+
|
|
17
|
+
# pyPKIPC
|
|
18
|
+
|
|
19
|
+
`pyPKIPC 2` 是用于父子进程通信的轻量事件运行时。父进程负责启动子进程,双方通过一条长期 gRPC 双向流收发任意 JSON 事件。
|
|
20
|
+
|
|
21
|
+
运行时不使用 `stdin/stdout` 传输,不定义应用层握手,也没有旧协议兼容代码。
|
|
22
|
+
|
|
23
|
+
## 安装
|
|
24
|
+
|
|
25
|
+
```powershell
|
|
26
|
+
python -m pip install PKIPC
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
本地开发和打包依赖:
|
|
30
|
+
|
|
31
|
+
```powershell
|
|
32
|
+
python -m pip install -e .[test,build]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 子进程:Server
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import threading
|
|
39
|
+
|
|
40
|
+
from pkipc import Server
|
|
41
|
+
|
|
42
|
+
server = Server()
|
|
43
|
+
stopped = threading.Event()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@server.on("solver.add")
|
|
47
|
+
def add(data):
|
|
48
|
+
return {"result": data["a"] + data["b"]}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@server.on("worker.stop")
|
|
52
|
+
def stop(_data):
|
|
53
|
+
stopped.set()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
config = server.start()
|
|
57
|
+
server.send(
|
|
58
|
+
"pkipc.log",
|
|
59
|
+
{
|
|
60
|
+
"level": "INFO",
|
|
61
|
+
"message": f"worker started with {config}",
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
while not stopped.wait(0.1) and not server.stop_requested():
|
|
65
|
+
pass
|
|
66
|
+
server.close()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`start()` 建立 gRPC 流并返回父进程下发的配置。事件回调在单一接收线程中按顺序执行。
|
|
70
|
+
|
|
71
|
+
## 父进程:Client
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import sys
|
|
75
|
+
|
|
76
|
+
from pkipc import Client
|
|
77
|
+
|
|
78
|
+
client = Client(
|
|
79
|
+
sys.executable,
|
|
80
|
+
args=("worker.py",),
|
|
81
|
+
config={"value": 42},
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@client.on("pkipc.log")
|
|
86
|
+
def log_received(data):
|
|
87
|
+
print(data["level"], data["message"])
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
client.start()
|
|
91
|
+
result = client.request(
|
|
92
|
+
"solver.add",
|
|
93
|
+
{
|
|
94
|
+
"a": 10,
|
|
95
|
+
"b": 20,
|
|
96
|
+
},
|
|
97
|
+
timeout=5.0,
|
|
98
|
+
)
|
|
99
|
+
print(result)
|
|
100
|
+
client.send("worker.stop")
|
|
101
|
+
client.wait()
|
|
102
|
+
client.close()
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
父子两端使用完全相同的 `send(event, data)`、`request(event, data, timeout)` 和 `on(event)`。`send()` 是不等待结果的单向事件;`request()` 阻塞等待对应 handler 的返回值,请求 ID 和响应匹配完全由 Runtime 管理。框架不提供 `send_data`、`send_log`、`on_data` 或 `on_log`;业务层可以按需用普通函数做薄封装。
|
|
106
|
+
|
|
107
|
+
`request()` 不能在 `@on` handler 内调用,因为 handler 运行在单一接收线程中;Runtime 会直接抛出 `RuntimeError`,避免嵌套同步请求死锁。请求超时抛出 `TimeoutError`,断连时所有等待中的请求抛出 `ConnectionError`,远端 handler 异常则在调用端表现为 `RuntimeError`。
|
|
108
|
+
|
|
109
|
+
框架不定义自有异常类型:无效事件抛出 `ValueError`,断连抛出 `ConnectionError`,启动等待超时抛出 `TimeoutError`,子进程提前退出抛出 `ChildProcessError`,重复启动等生命周期错误抛出 `RuntimeError`。
|
|
110
|
+
|
|
111
|
+
## 运行模型
|
|
112
|
+
|
|
113
|
+
1. 父进程在 `127.0.0.1:0` 启动专属 gRPC Server。
|
|
114
|
+
2. 父进程生成随机 token,通过环境变量把 endpoint 和 token 传给子进程。
|
|
115
|
+
3. 子进程建立 `Runtime.Run` 双向流。
|
|
116
|
+
4. 父进程发送的第一帧固定为 `pkipc.config`,由 `Server.start()` 消费。
|
|
117
|
+
5. 配置完成后,父子进程通过同样的 Frame 双向收发任意命名事件。
|
|
118
|
+
6. 流结束即表示停止,错误由 gRPC status 表达。
|
|
119
|
+
|
|
120
|
+
传输层只有一个无类型消息和一个 RPC:
|
|
121
|
+
|
|
122
|
+
```protobuf
|
|
123
|
+
service Runtime {
|
|
124
|
+
rpc Run(stream Frame) returns (stream Frame);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
message Frame {
|
|
128
|
+
bytes payload = 1;
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`Frame.payload` 是 UTF-8 JSON,统一由 Runtime 层编码、校验和分发:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{"event":"pkipc.config","data":{"threads":8}}
|
|
136
|
+
{"event":"pkipc.control","data":{"action":"PAUSE"}}
|
|
137
|
+
{"event":"pkipc.data","data":{"progress":0.5}}
|
|
138
|
+
{"event":"pkipc.log","data":{"level":"INFO","message":"solver started"}}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
同步请求增加 `id`,响应使用内部事件 `pkipc.response` 和 `reply_to`:
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{"event":"solver.add","data":{"a":10,"b":20},"id":"request-id"}
|
|
145
|
+
{"event":"pkipc.response","data":{"result":30},"reply_to":"request-id"}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`event` 必须是非空字符串,`data` 必须存在且可以是任意合法 JSON。`id`、`reply_to` 和错误响应由 Runtime 独占管理。`pkipc.config` 由启动流程管理;`pkipc.control`、`pkipc.data` 和 `pkipc.log` 是经过校验的标准事件约定,但不绑定专用 Python API。自定义事件名会原样传输,不需要修改或重新生成 Protobuf。
|
|
149
|
+
|
|
150
|
+
协议定义在:
|
|
151
|
+
|
|
152
|
+
- `pkipc/proto/pkipc.proto`
|
|
153
|
+
|
|
154
|
+
Python 生成代码和 `.proto` 一起发布。C++ 实现只需要从同一份 `.proto` 生成传输 stub,并在 Runtime dispatcher 中实现相同的 JSON 信封约定。
|
|
155
|
+
|
|
156
|
+
## 完整示例
|
|
157
|
+
|
|
158
|
+
- `examples/01_request_response/`:同步请求响应,实现计算器
|
|
159
|
+
- `examples/02_commands/`:单向命令,实现暂停、恢复和停止
|
|
160
|
+
- `examples/03_progress/`:长任务持续推送进度,最后返回结果
|
|
161
|
+
- `examples/04_reverse_request/`:Server 反向请求 Client
|
|
162
|
+
- `examples/05_errors/`:请求超时、连接断开和远端 handler 异常
|
|
163
|
+
|
|
164
|
+
每个目录包含一组 `client.py` 和 `server.py`。只运行 `client.py`,Client 会负责启动
|
|
165
|
+
Server 子进程:
|
|
166
|
+
|
|
167
|
+
```powershell
|
|
168
|
+
python examples/01_request_response/client.py
|
|
169
|
+
python examples/02_commands/client.py
|
|
170
|
+
python examples/03_progress/client.py
|
|
171
|
+
python examples/04_reverse_request/client.py
|
|
172
|
+
python examples/05_errors/client.py
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
完整说明见 `examples/README.md`。
|
|
176
|
+
|
|
177
|
+
## 测试
|
|
178
|
+
|
|
179
|
+
```powershell
|
|
180
|
+
python -m pytest -q
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
测试覆盖单一 Frame 协议、信封校验、双向通用事件分发、五组完整示例、启动失败、
|
|
184
|
+
stdout 独立性、主动关闭和子进程异常退出。
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PKIPC
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Event runtime over gRPC for parent-child process communication
|
|
5
|
+
Author: PKIPC
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: grpcio<2,>=1.81.1
|
|
9
|
+
Requires-Dist: protobuf<7,>=6.33.5
|
|
10
|
+
Provides-Extra: test
|
|
11
|
+
Requires-Dist: pytest>=8.2; extra == "test"
|
|
12
|
+
Requires-Dist: ruff>=0.12; extra == "test"
|
|
13
|
+
Provides-Extra: build
|
|
14
|
+
Requires-Dist: grpcio-tools<2,>=1.81.1; extra == "build"
|
|
15
|
+
Requires-Dist: pyinstaller>=6.0; extra == "build"
|
|
16
|
+
|
|
17
|
+
# pyPKIPC
|
|
18
|
+
|
|
19
|
+
`pyPKIPC 2` 是用于父子进程通信的轻量事件运行时。父进程负责启动子进程,双方通过一条长期 gRPC 双向流收发任意 JSON 事件。
|
|
20
|
+
|
|
21
|
+
运行时不使用 `stdin/stdout` 传输,不定义应用层握手,也没有旧协议兼容代码。
|
|
22
|
+
|
|
23
|
+
## 安装
|
|
24
|
+
|
|
25
|
+
```powershell
|
|
26
|
+
python -m pip install PKIPC
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
本地开发和打包依赖:
|
|
30
|
+
|
|
31
|
+
```powershell
|
|
32
|
+
python -m pip install -e .[test,build]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 子进程:Server
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import threading
|
|
39
|
+
|
|
40
|
+
from pkipc import Server
|
|
41
|
+
|
|
42
|
+
server = Server()
|
|
43
|
+
stopped = threading.Event()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@server.on("solver.add")
|
|
47
|
+
def add(data):
|
|
48
|
+
return {"result": data["a"] + data["b"]}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@server.on("worker.stop")
|
|
52
|
+
def stop(_data):
|
|
53
|
+
stopped.set()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
config = server.start()
|
|
57
|
+
server.send(
|
|
58
|
+
"pkipc.log",
|
|
59
|
+
{
|
|
60
|
+
"level": "INFO",
|
|
61
|
+
"message": f"worker started with {config}",
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
while not stopped.wait(0.1) and not server.stop_requested():
|
|
65
|
+
pass
|
|
66
|
+
server.close()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`start()` 建立 gRPC 流并返回父进程下发的配置。事件回调在单一接收线程中按顺序执行。
|
|
70
|
+
|
|
71
|
+
## 父进程:Client
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import sys
|
|
75
|
+
|
|
76
|
+
from pkipc import Client
|
|
77
|
+
|
|
78
|
+
client = Client(
|
|
79
|
+
sys.executable,
|
|
80
|
+
args=("worker.py",),
|
|
81
|
+
config={"value": 42},
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@client.on("pkipc.log")
|
|
86
|
+
def log_received(data):
|
|
87
|
+
print(data["level"], data["message"])
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
client.start()
|
|
91
|
+
result = client.request(
|
|
92
|
+
"solver.add",
|
|
93
|
+
{
|
|
94
|
+
"a": 10,
|
|
95
|
+
"b": 20,
|
|
96
|
+
},
|
|
97
|
+
timeout=5.0,
|
|
98
|
+
)
|
|
99
|
+
print(result)
|
|
100
|
+
client.send("worker.stop")
|
|
101
|
+
client.wait()
|
|
102
|
+
client.close()
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
父子两端使用完全相同的 `send(event, data)`、`request(event, data, timeout)` 和 `on(event)`。`send()` 是不等待结果的单向事件;`request()` 阻塞等待对应 handler 的返回值,请求 ID 和响应匹配完全由 Runtime 管理。框架不提供 `send_data`、`send_log`、`on_data` 或 `on_log`;业务层可以按需用普通函数做薄封装。
|
|
106
|
+
|
|
107
|
+
`request()` 不能在 `@on` handler 内调用,因为 handler 运行在单一接收线程中;Runtime 会直接抛出 `RuntimeError`,避免嵌套同步请求死锁。请求超时抛出 `TimeoutError`,断连时所有等待中的请求抛出 `ConnectionError`,远端 handler 异常则在调用端表现为 `RuntimeError`。
|
|
108
|
+
|
|
109
|
+
框架不定义自有异常类型:无效事件抛出 `ValueError`,断连抛出 `ConnectionError`,启动等待超时抛出 `TimeoutError`,子进程提前退出抛出 `ChildProcessError`,重复启动等生命周期错误抛出 `RuntimeError`。
|
|
110
|
+
|
|
111
|
+
## 运行模型
|
|
112
|
+
|
|
113
|
+
1. 父进程在 `127.0.0.1:0` 启动专属 gRPC Server。
|
|
114
|
+
2. 父进程生成随机 token,通过环境变量把 endpoint 和 token 传给子进程。
|
|
115
|
+
3. 子进程建立 `Runtime.Run` 双向流。
|
|
116
|
+
4. 父进程发送的第一帧固定为 `pkipc.config`,由 `Server.start()` 消费。
|
|
117
|
+
5. 配置完成后,父子进程通过同样的 Frame 双向收发任意命名事件。
|
|
118
|
+
6. 流结束即表示停止,错误由 gRPC status 表达。
|
|
119
|
+
|
|
120
|
+
传输层只有一个无类型消息和一个 RPC:
|
|
121
|
+
|
|
122
|
+
```protobuf
|
|
123
|
+
service Runtime {
|
|
124
|
+
rpc Run(stream Frame) returns (stream Frame);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
message Frame {
|
|
128
|
+
bytes payload = 1;
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`Frame.payload` 是 UTF-8 JSON,统一由 Runtime 层编码、校验和分发:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{"event":"pkipc.config","data":{"threads":8}}
|
|
136
|
+
{"event":"pkipc.control","data":{"action":"PAUSE"}}
|
|
137
|
+
{"event":"pkipc.data","data":{"progress":0.5}}
|
|
138
|
+
{"event":"pkipc.log","data":{"level":"INFO","message":"solver started"}}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
同步请求增加 `id`,响应使用内部事件 `pkipc.response` 和 `reply_to`:
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{"event":"solver.add","data":{"a":10,"b":20},"id":"request-id"}
|
|
145
|
+
{"event":"pkipc.response","data":{"result":30},"reply_to":"request-id"}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`event` 必须是非空字符串,`data` 必须存在且可以是任意合法 JSON。`id`、`reply_to` 和错误响应由 Runtime 独占管理。`pkipc.config` 由启动流程管理;`pkipc.control`、`pkipc.data` 和 `pkipc.log` 是经过校验的标准事件约定,但不绑定专用 Python API。自定义事件名会原样传输,不需要修改或重新生成 Protobuf。
|
|
149
|
+
|
|
150
|
+
协议定义在:
|
|
151
|
+
|
|
152
|
+
- `pkipc/proto/pkipc.proto`
|
|
153
|
+
|
|
154
|
+
Python 生成代码和 `.proto` 一起发布。C++ 实现只需要从同一份 `.proto` 生成传输 stub,并在 Runtime dispatcher 中实现相同的 JSON 信封约定。
|
|
155
|
+
|
|
156
|
+
## 完整示例
|
|
157
|
+
|
|
158
|
+
- `examples/01_request_response/`:同步请求响应,实现计算器
|
|
159
|
+
- `examples/02_commands/`:单向命令,实现暂停、恢复和停止
|
|
160
|
+
- `examples/03_progress/`:长任务持续推送进度,最后返回结果
|
|
161
|
+
- `examples/04_reverse_request/`:Server 反向请求 Client
|
|
162
|
+
- `examples/05_errors/`:请求超时、连接断开和远端 handler 异常
|
|
163
|
+
|
|
164
|
+
每个目录包含一组 `client.py` 和 `server.py`。只运行 `client.py`,Client 会负责启动
|
|
165
|
+
Server 子进程:
|
|
166
|
+
|
|
167
|
+
```powershell
|
|
168
|
+
python examples/01_request_response/client.py
|
|
169
|
+
python examples/02_commands/client.py
|
|
170
|
+
python examples/03_progress/client.py
|
|
171
|
+
python examples/04_reverse_request/client.py
|
|
172
|
+
python examples/05_errors/client.py
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
完整说明见 `examples/README.md`。
|
|
176
|
+
|
|
177
|
+
## 测试
|
|
178
|
+
|
|
179
|
+
```powershell
|
|
180
|
+
python -m pytest -q
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
测试覆盖单一 Frame 协议、信封校验、双向通用事件分发、五组完整示例、启动失败、
|
|
184
|
+
stdout 独立性、主动关闭和子进程异常退出。
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
PKIPC.egg-info/PKG-INFO
|
|
4
|
+
PKIPC.egg-info/SOURCES.txt
|
|
5
|
+
PKIPC.egg-info/dependency_links.txt
|
|
6
|
+
PKIPC.egg-info/requires.txt
|
|
7
|
+
PKIPC.egg-info/top_level.txt
|
|
8
|
+
pkipc/__init__.py
|
|
9
|
+
pkipc/_grpc_common.py
|
|
10
|
+
pkipc/_grpc_host.py
|
|
11
|
+
pkipc/_runtime.py
|
|
12
|
+
pkipc/_stream.py
|
|
13
|
+
pkipc/client.py
|
|
14
|
+
pkipc/server.py
|
|
15
|
+
pkipc/proto/__init__.py
|
|
16
|
+
pkipc/proto/pkipc.proto
|
|
17
|
+
pkipc/proto/pkipc_pb2.py
|
|
18
|
+
pkipc/proto/pkipc_pb2.pyi
|
|
19
|
+
pkipc/proto/pkipc_pb2_grpc.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pkipc
|
pkipc-2.0.0/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# pyPKIPC
|
|
2
|
+
|
|
3
|
+
`pyPKIPC 2` 是用于父子进程通信的轻量事件运行时。父进程负责启动子进程,双方通过一条长期 gRPC 双向流收发任意 JSON 事件。
|
|
4
|
+
|
|
5
|
+
运行时不使用 `stdin/stdout` 传输,不定义应用层握手,也没有旧协议兼容代码。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```powershell
|
|
10
|
+
python -m pip install PKIPC
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
本地开发和打包依赖:
|
|
14
|
+
|
|
15
|
+
```powershell
|
|
16
|
+
python -m pip install -e .[test,build]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 子进程:Server
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import threading
|
|
23
|
+
|
|
24
|
+
from pkipc import Server
|
|
25
|
+
|
|
26
|
+
server = Server()
|
|
27
|
+
stopped = threading.Event()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@server.on("solver.add")
|
|
31
|
+
def add(data):
|
|
32
|
+
return {"result": data["a"] + data["b"]}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@server.on("worker.stop")
|
|
36
|
+
def stop(_data):
|
|
37
|
+
stopped.set()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
config = server.start()
|
|
41
|
+
server.send(
|
|
42
|
+
"pkipc.log",
|
|
43
|
+
{
|
|
44
|
+
"level": "INFO",
|
|
45
|
+
"message": f"worker started with {config}",
|
|
46
|
+
},
|
|
47
|
+
)
|
|
48
|
+
while not stopped.wait(0.1) and not server.stop_requested():
|
|
49
|
+
pass
|
|
50
|
+
server.close()
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`start()` 建立 gRPC 流并返回父进程下发的配置。事件回调在单一接收线程中按顺序执行。
|
|
54
|
+
|
|
55
|
+
## 父进程:Client
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import sys
|
|
59
|
+
|
|
60
|
+
from pkipc import Client
|
|
61
|
+
|
|
62
|
+
client = Client(
|
|
63
|
+
sys.executable,
|
|
64
|
+
args=("worker.py",),
|
|
65
|
+
config={"value": 42},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@client.on("pkipc.log")
|
|
70
|
+
def log_received(data):
|
|
71
|
+
print(data["level"], data["message"])
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
client.start()
|
|
75
|
+
result = client.request(
|
|
76
|
+
"solver.add",
|
|
77
|
+
{
|
|
78
|
+
"a": 10,
|
|
79
|
+
"b": 20,
|
|
80
|
+
},
|
|
81
|
+
timeout=5.0,
|
|
82
|
+
)
|
|
83
|
+
print(result)
|
|
84
|
+
client.send("worker.stop")
|
|
85
|
+
client.wait()
|
|
86
|
+
client.close()
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
父子两端使用完全相同的 `send(event, data)`、`request(event, data, timeout)` 和 `on(event)`。`send()` 是不等待结果的单向事件;`request()` 阻塞等待对应 handler 的返回值,请求 ID 和响应匹配完全由 Runtime 管理。框架不提供 `send_data`、`send_log`、`on_data` 或 `on_log`;业务层可以按需用普通函数做薄封装。
|
|
90
|
+
|
|
91
|
+
`request()` 不能在 `@on` handler 内调用,因为 handler 运行在单一接收线程中;Runtime 会直接抛出 `RuntimeError`,避免嵌套同步请求死锁。请求超时抛出 `TimeoutError`,断连时所有等待中的请求抛出 `ConnectionError`,远端 handler 异常则在调用端表现为 `RuntimeError`。
|
|
92
|
+
|
|
93
|
+
框架不定义自有异常类型:无效事件抛出 `ValueError`,断连抛出 `ConnectionError`,启动等待超时抛出 `TimeoutError`,子进程提前退出抛出 `ChildProcessError`,重复启动等生命周期错误抛出 `RuntimeError`。
|
|
94
|
+
|
|
95
|
+
## 运行模型
|
|
96
|
+
|
|
97
|
+
1. 父进程在 `127.0.0.1:0` 启动专属 gRPC Server。
|
|
98
|
+
2. 父进程生成随机 token,通过环境变量把 endpoint 和 token 传给子进程。
|
|
99
|
+
3. 子进程建立 `Runtime.Run` 双向流。
|
|
100
|
+
4. 父进程发送的第一帧固定为 `pkipc.config`,由 `Server.start()` 消费。
|
|
101
|
+
5. 配置完成后,父子进程通过同样的 Frame 双向收发任意命名事件。
|
|
102
|
+
6. 流结束即表示停止,错误由 gRPC status 表达。
|
|
103
|
+
|
|
104
|
+
传输层只有一个无类型消息和一个 RPC:
|
|
105
|
+
|
|
106
|
+
```protobuf
|
|
107
|
+
service Runtime {
|
|
108
|
+
rpc Run(stream Frame) returns (stream Frame);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
message Frame {
|
|
112
|
+
bytes payload = 1;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`Frame.payload` 是 UTF-8 JSON,统一由 Runtime 层编码、校验和分发:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{"event":"pkipc.config","data":{"threads":8}}
|
|
120
|
+
{"event":"pkipc.control","data":{"action":"PAUSE"}}
|
|
121
|
+
{"event":"pkipc.data","data":{"progress":0.5}}
|
|
122
|
+
{"event":"pkipc.log","data":{"level":"INFO","message":"solver started"}}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
同步请求增加 `id`,响应使用内部事件 `pkipc.response` 和 `reply_to`:
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{"event":"solver.add","data":{"a":10,"b":20},"id":"request-id"}
|
|
129
|
+
{"event":"pkipc.response","data":{"result":30},"reply_to":"request-id"}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`event` 必须是非空字符串,`data` 必须存在且可以是任意合法 JSON。`id`、`reply_to` 和错误响应由 Runtime 独占管理。`pkipc.config` 由启动流程管理;`pkipc.control`、`pkipc.data` 和 `pkipc.log` 是经过校验的标准事件约定,但不绑定专用 Python API。自定义事件名会原样传输,不需要修改或重新生成 Protobuf。
|
|
133
|
+
|
|
134
|
+
协议定义在:
|
|
135
|
+
|
|
136
|
+
- `pkipc/proto/pkipc.proto`
|
|
137
|
+
|
|
138
|
+
Python 生成代码和 `.proto` 一起发布。C++ 实现只需要从同一份 `.proto` 生成传输 stub,并在 Runtime dispatcher 中实现相同的 JSON 信封约定。
|
|
139
|
+
|
|
140
|
+
## 完整示例
|
|
141
|
+
|
|
142
|
+
- `examples/01_request_response/`:同步请求响应,实现计算器
|
|
143
|
+
- `examples/02_commands/`:单向命令,实现暂停、恢复和停止
|
|
144
|
+
- `examples/03_progress/`:长任务持续推送进度,最后返回结果
|
|
145
|
+
- `examples/04_reverse_request/`:Server 反向请求 Client
|
|
146
|
+
- `examples/05_errors/`:请求超时、连接断开和远端 handler 异常
|
|
147
|
+
|
|
148
|
+
每个目录包含一组 `client.py` 和 `server.py`。只运行 `client.py`,Client 会负责启动
|
|
149
|
+
Server 子进程:
|
|
150
|
+
|
|
151
|
+
```powershell
|
|
152
|
+
python examples/01_request_response/client.py
|
|
153
|
+
python examples/02_commands/client.py
|
|
154
|
+
python examples/03_progress/client.py
|
|
155
|
+
python examples/04_reverse_request/client.py
|
|
156
|
+
python examples/05_errors/client.py
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
完整说明见 `examples/README.md`。
|
|
160
|
+
|
|
161
|
+
## 测试
|
|
162
|
+
|
|
163
|
+
```powershell
|
|
164
|
+
python -m pytest -q
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
测试覆盖单一 Frame 协议、信封校验、双向通用事件分发、五组完整示例、启动失败、
|
|
168
|
+
stdout 独立性、主动关闭和子进程异常退出。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
ENDPOINT_ENV = "PKIPC_ENDPOINT"
|
|
4
|
+
TOKEN_ENV = "PKIPC_TOKEN"
|
|
5
|
+
TOKEN_METADATA_KEY = "x-pkipc-token"
|
|
6
|
+
MAX_MESSAGE_BYTES = 16 * 1024 * 1024
|
|
7
|
+
OUTGOING_QUEUE_SIZE = 256
|
|
8
|
+
|
|
9
|
+
GRPC_OPTIONS: tuple[tuple[str, int], ...] = (
|
|
10
|
+
("grpc.max_receive_message_length", MAX_MESSAGE_BYTES),
|
|
11
|
+
("grpc.max_send_message_length", MAX_MESSAGE_BYTES),
|
|
12
|
+
)
|