portal 3.1.5__tar.gz → 3.1.7__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.
- {portal-3.1.5/portal.egg-info → portal-3.1.7}/PKG-INFO +1 -1
- {portal-3.1.5 → portal-3.1.7}/portal/__init__.py +1 -1
- {portal-3.1.5 → portal-3.1.7}/portal/client.py +35 -12
- {portal-3.1.5 → portal-3.1.7}/portal/client_socket.py +12 -3
- {portal-3.1.5 → portal-3.1.7}/portal/contextlib.py +5 -3
- {portal-3.1.5 → portal-3.1.7}/portal/server_socket.py +2 -0
- {portal-3.1.5 → portal-3.1.7/portal.egg-info}/PKG-INFO +1 -1
- {portal-3.1.5 → portal-3.1.7}/tests/test_client.py +16 -2
- {portal-3.1.5 → portal-3.1.7}/LICENSE +0 -0
- {portal-3.1.5 → portal-3.1.7}/MANIFEST.in +0 -0
- {portal-3.1.5 → portal-3.1.7}/README.md +0 -0
- {portal-3.1.5 → portal-3.1.7}/portal/batching.py +0 -0
- {portal-3.1.5 → portal-3.1.7}/portal/buffers.py +0 -0
- {portal-3.1.5 → portal-3.1.7}/portal/packlib.py +0 -0
- {portal-3.1.5 → portal-3.1.7}/portal/poollib.py +0 -0
- {portal-3.1.5 → portal-3.1.7}/portal/process.py +0 -0
- {portal-3.1.5 → portal-3.1.7}/portal/server.py +0 -0
- {portal-3.1.5 → portal-3.1.7}/portal/sharray.py +0 -0
- {portal-3.1.5 → portal-3.1.7}/portal/thread.py +0 -0
- {portal-3.1.5 → portal-3.1.7}/portal/utils.py +0 -0
- {portal-3.1.5 → portal-3.1.7}/portal.egg-info/SOURCES.txt +0 -0
- {portal-3.1.5 → portal-3.1.7}/portal.egg-info/dependency_links.txt +0 -0
- {portal-3.1.5 → portal-3.1.7}/portal.egg-info/requires.txt +0 -0
- {portal-3.1.5 → portal-3.1.7}/portal.egg-info/top_level.txt +0 -0
- {portal-3.1.5 → portal-3.1.7}/pyproject.toml +0 -0
- {portal-3.1.5 → portal-3.1.7}/requirements.txt +0 -0
- {portal-3.1.5 → portal-3.1.7}/setup.cfg +0 -0
- {portal-3.1.5 → portal-3.1.7}/setup.py +0 -0
- {portal-3.1.5 → portal-3.1.7}/tests/test_batching.py +0 -0
- {portal-3.1.5 → portal-3.1.7}/tests/test_errfile.py +0 -0
- {portal-3.1.5 → portal-3.1.7}/tests/test_pack.py +0 -0
- {portal-3.1.5 → portal-3.1.7}/tests/test_process.py +0 -0
- {portal-3.1.5 → portal-3.1.7}/tests/test_server.py +0 -0
- {portal-3.1.5 → portal-3.1.7}/tests/test_socket.py +0 -0
- {portal-3.1.5 → portal-3.1.7}/tests/test_thread.py +0 -0
@@ -81,10 +81,18 @@ class Client:
|
|
81
81
|
strlen = len(name).to_bytes(8, 'little', signed=False)
|
82
82
|
sendargs = (reqnum, strlen, name, *packlib.pack(data))
|
83
83
|
# self.socket.send(reqnum, strlen, name, *packlib.pack(data))
|
84
|
-
|
85
|
-
future = Future()
|
84
|
+
rai = [False]
|
85
|
+
future = Future(rai)
|
86
86
|
future.sendargs = sendargs
|
87
87
|
self.futures[reqnum] = future
|
88
|
+
# Store future before sending request because the response may come fast
|
89
|
+
# and the response handler runs in the socket's background thread.
|
90
|
+
try:
|
91
|
+
self.socket.send(*sendargs)
|
92
|
+
except client_socket.Disconnected:
|
93
|
+
future = self.futures.pop(reqnum)
|
94
|
+
future.rai[0] = True
|
95
|
+
raise
|
88
96
|
return future
|
89
97
|
|
90
98
|
def close(self, timeout=None):
|
@@ -98,9 +106,11 @@ class Client:
|
|
98
106
|
reqnum = bytes(data[:8])
|
99
107
|
status = int.from_bytes(data[8:16], 'little', signed=False)
|
100
108
|
future = self.futures.pop(reqnum, None)
|
109
|
+
|
101
110
|
assert future, (
|
102
111
|
f'Unexpected request number: {reqnum}',
|
103
112
|
sorted(self.futures.keys()))
|
113
|
+
|
104
114
|
if status == 0:
|
105
115
|
data = packlib.unpack(data[16:])
|
106
116
|
future.set_result(data)
|
@@ -111,12 +121,25 @@ class Client:
|
|
111
121
|
self.cond.notify_all()
|
112
122
|
self.socket.recv()
|
113
123
|
|
124
|
+
# if not future:
|
125
|
+
# existing = sorted(self.futures.keys())
|
126
|
+
# print(f'Unexpected request number: {reqnum}', existing)
|
127
|
+
# elif status == 0:
|
128
|
+
# data = packlib.unpack(data[16:])
|
129
|
+
# future.set_result(data)
|
130
|
+
# with self.cond: self.cond.notify_all()
|
131
|
+
# else:
|
132
|
+
# message = bytes(data[16:]).decode('utf-8')
|
133
|
+
# self._seterr(future, RuntimeError(message))
|
134
|
+
# with self.cond: self.cond.notify_all()
|
135
|
+
# self.socket.recv()
|
136
|
+
|
114
137
|
def _disc(self):
|
115
138
|
if self.socket.options.autoconn:
|
116
|
-
for future in self.futures.values():
|
139
|
+
for future in list(self.futures.values()):
|
117
140
|
future.resend = True
|
118
141
|
else:
|
119
|
-
for future in self.futures.values():
|
142
|
+
for future in list(self.futures.values()):
|
120
143
|
self._seterr(future, client_socket.Disconnected)
|
121
144
|
self.futures.clear()
|
122
145
|
|
@@ -127,17 +150,17 @@ class Client:
|
|
127
150
|
self.socket.send(*future.sendargs)
|
128
151
|
|
129
152
|
def _seterr(self, future, e):
|
130
|
-
raised = [False]
|
131
|
-
future.raised = raised
|
132
153
|
future.set_error(e)
|
154
|
+
rai = future.rai
|
133
155
|
weakref.finalize(future, lambda: (
|
134
|
-
None if
|
156
|
+
None if rai[0] else self.errors.append(e)))
|
135
157
|
|
136
158
|
|
137
159
|
class Future:
|
138
160
|
|
139
|
-
def __init__(self):
|
140
|
-
|
161
|
+
def __init__(self, rai):
|
162
|
+
assert rai == [False]
|
163
|
+
self.rai = rai
|
141
164
|
self.con = threading.Condition()
|
142
165
|
self.don = False
|
143
166
|
self.res = None
|
@@ -147,7 +170,7 @@ class Future:
|
|
147
170
|
if not self.done:
|
148
171
|
return 'Future(done=False)'
|
149
172
|
elif self.err:
|
150
|
-
return f"Future(done=True, error='{self.err}', raised={self.
|
173
|
+
return f"Future(done=True, error='{self.err}', raised={self.rai[0]})"
|
151
174
|
else:
|
152
175
|
return 'Future(done=True)'
|
153
176
|
|
@@ -165,8 +188,8 @@ class Future:
|
|
165
188
|
assert self.don
|
166
189
|
if self.err is None:
|
167
190
|
return self.res
|
168
|
-
if not self.
|
169
|
-
self.
|
191
|
+
if not self.rai[0]:
|
192
|
+
self.rai[0] = True
|
170
193
|
raise self.err
|
171
194
|
|
172
195
|
def set_result(self, result):
|
@@ -110,6 +110,7 @@ class ClientSocket:
|
|
110
110
|
def _loop(self):
|
111
111
|
recvbuf = buffers.RecvBuffer(maxsize=self.options.max_msg_size)
|
112
112
|
sock = None
|
113
|
+
poll = select.poll()
|
113
114
|
isconn = False # Local mirror of self.isconn without the lock.
|
114
115
|
|
115
116
|
while self.running or (self.sendq and isconn):
|
@@ -120,6 +121,7 @@ class ClientSocket:
|
|
120
121
|
sock = self._connect()
|
121
122
|
if not sock:
|
122
123
|
break
|
124
|
+
poll.register(sock, select.POLLIN | select.POLLOUT)
|
123
125
|
self.isconn.set()
|
124
126
|
isconn = True
|
125
127
|
if not self.options.autoconn:
|
@@ -128,9 +130,15 @@ class ClientSocket:
|
|
128
130
|
|
129
131
|
try:
|
130
132
|
|
131
|
-
|
133
|
+
# TODO: According to the py-spy profiler, the GIL is held during
|
134
|
+
# polling. Is there a way to avoid that?
|
135
|
+
pairs = poll.poll(0.2)
|
136
|
+
if not pairs:
|
137
|
+
continue
|
138
|
+
_, mask = pairs[0]
|
139
|
+
|
132
140
|
|
133
|
-
if
|
141
|
+
if mask & select.POLLIN:
|
134
142
|
try:
|
135
143
|
recvbuf.recv(sock)
|
136
144
|
if recvbuf.done():
|
@@ -143,7 +151,7 @@ class ClientSocket:
|
|
143
151
|
except BlockingIOError:
|
144
152
|
pass
|
145
153
|
|
146
|
-
if self.sendq and
|
154
|
+
if self.sendq and mask & select.POLLOUT:
|
147
155
|
try:
|
148
156
|
self.sendq[0].send(sock)
|
149
157
|
if self.sendq[0].done():
|
@@ -157,6 +165,7 @@ class ClientSocket:
|
|
157
165
|
self._log(f'Connection to server lost ({detail})')
|
158
166
|
self.isconn.clear()
|
159
167
|
isconn = False
|
168
|
+
poll.unregister(sock)
|
160
169
|
sock.close()
|
161
170
|
# Clear message queue on disconnect. There is no meaningful concept of
|
162
171
|
# sucessful delivery of a message at this level. For example, the
|
@@ -2,6 +2,7 @@ import collections
|
|
2
2
|
import multiprocessing as mp
|
3
3
|
import os
|
4
4
|
import pathlib
|
5
|
+
import sys
|
5
6
|
import threading
|
6
7
|
import traceback
|
7
8
|
|
@@ -114,10 +115,10 @@ class Context:
|
|
114
115
|
with self.printlock:
|
115
116
|
style = utils.style(color='red')
|
116
117
|
reset = utils.style(reset=True)
|
117
|
-
print(style + '\n---\n' + message + reset)
|
118
|
+
print(style + '\n---\n' + message + reset, file=sys.stderr)
|
118
119
|
if self.errfile:
|
119
120
|
self.errfile.write_text(message)
|
120
|
-
print(f'Wrote errorfile: {self.errfile}')
|
121
|
+
print(f'Wrote errorfile: {self.errfile}', file=sys.stderr)
|
121
122
|
|
122
123
|
def shutdown(self, exitcode):
|
123
124
|
# This kills the process tree forcefully to prevent hangs but results in
|
@@ -145,7 +146,8 @@ class Context:
|
|
145
146
|
def _watcher(self):
|
146
147
|
while True:
|
147
148
|
if self.errfile and self.errfile.exists():
|
148
|
-
|
149
|
+
message = f'Shutting down due to error file: {self.errfile}',
|
150
|
+
print(message, file=sys.stderr)
|
149
151
|
self.shutdown(2)
|
150
152
|
break
|
151
153
|
if self.done.wait(self.interval):
|
@@ -101,6 +101,8 @@ class ServerSocket:
|
|
101
101
|
try:
|
102
102
|
while self.running or self._numsending():
|
103
103
|
writeable = []
|
104
|
+
# TODO: According to the py-spy profiler, the GIL is held during
|
105
|
+
# polling. Is there a way to avoid that?
|
104
106
|
for key, mask in self.sel.select(timeout=0.2):
|
105
107
|
if key.data is None and self.reading:
|
106
108
|
assert mask & selectors.EVENT_READ
|
@@ -48,8 +48,22 @@ class TestClient:
|
|
48
48
|
client.connect()
|
49
49
|
assert client.fn(1).result() == 1
|
50
50
|
server.close()
|
51
|
-
|
52
|
-
|
51
|
+
|
52
|
+
assert len(client.futures) == 0
|
53
|
+
assert len(client.errors) == 0
|
54
|
+
try:
|
55
|
+
future = client.fn(2)
|
56
|
+
try:
|
57
|
+
future.result()
|
58
|
+
assert False
|
59
|
+
except portal.Disconnected:
|
60
|
+
assert True
|
61
|
+
except portal.Disconnected:
|
62
|
+
time.sleep(1)
|
63
|
+
future = None
|
64
|
+
assert len(client.futures) == 0
|
65
|
+
assert len(client.errors) == 0
|
66
|
+
|
53
67
|
server = portal.Server(port)
|
54
68
|
server.bind('fn', lambda x: x)
|
55
69
|
server.start(block=False)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|