portal 3.4.3__tar.gz → 3.5.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.
- {portal-3.4.3/portal.egg-info → portal-3.5.0}/PKG-INFO +1 -1
- {portal-3.4.3 → portal-3.5.0}/portal/__init__.py +1 -1
- {portal-3.4.3 → portal-3.5.0}/portal/client_socket.py +32 -25
- {portal-3.4.3 → portal-3.5.0}/portal/server_socket.py +20 -17
- {portal-3.4.3 → portal-3.5.0/portal.egg-info}/PKG-INFO +1 -1
- {portal-3.4.3 → portal-3.5.0}/LICENSE +0 -0
- {portal-3.4.3 → portal-3.5.0}/MANIFEST.in +0 -0
- {portal-3.4.3 → portal-3.5.0}/README.md +0 -0
- {portal-3.4.3 → portal-3.5.0}/portal/batching.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/portal/buffers.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/portal/client.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/portal/contextlib.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/portal/packlib.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/portal/poollib.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/portal/process.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/portal/server.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/portal/sharray.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/portal/thread.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/portal/utils.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/portal.egg-info/SOURCES.txt +0 -0
- {portal-3.4.3 → portal-3.5.0}/portal.egg-info/dependency_links.txt +0 -0
- {portal-3.4.3 → portal-3.5.0}/portal.egg-info/requires.txt +0 -0
- {portal-3.4.3 → portal-3.5.0}/portal.egg-info/top_level.txt +0 -0
- {portal-3.4.3 → portal-3.5.0}/pyproject.toml +0 -0
- {portal-3.4.3 → portal-3.5.0}/requirements.txt +0 -0
- {portal-3.4.3 → portal-3.5.0}/setup.cfg +0 -0
- {portal-3.4.3 → portal-3.5.0}/setup.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/tests/test_batching.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/tests/test_client.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/tests/test_errfile.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/tests/test_pack.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/tests/test_process.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/tests/test_server.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/tests/test_socket.py +0 -0
- {portal-3.4.3 → portal-3.5.0}/tests/test_thread.py +0 -0
@@ -1,5 +1,6 @@
|
|
1
1
|
import collections
|
2
2
|
import dataclasses
|
3
|
+
import os
|
3
4
|
import queue
|
4
5
|
import select
|
5
6
|
import socket
|
@@ -30,7 +31,6 @@ class Options:
|
|
30
31
|
logging: bool = True
|
31
32
|
logging_color: str = 'yellow'
|
32
33
|
connect_wait: float = 0.1
|
33
|
-
loop_sleep: float = 0.0
|
34
34
|
|
35
35
|
|
36
36
|
class ClientSocket:
|
@@ -52,6 +52,7 @@ class ClientSocket:
|
|
52
52
|
self.wantconn = threading.Event()
|
53
53
|
self.sendq = collections.deque()
|
54
54
|
self.recvq = queue.Queue()
|
55
|
+
self.get_signal, self.set_signal = os.pipe()
|
55
56
|
|
56
57
|
self.running = True
|
57
58
|
self.thread = thread.Thread(self._loop, name=f'{name}Loop')
|
@@ -76,6 +77,7 @@ class ClientSocket:
|
|
76
77
|
self.require_connection(timeout)
|
77
78
|
maxsize = self.options.max_msg_size
|
78
79
|
self.sendq.append(buffers.SendBuffer(*data, maxsize=maxsize))
|
80
|
+
os.write(self.set_signal, bytes(1))
|
79
81
|
|
80
82
|
def recv(self, timeout=None):
|
81
83
|
assert self.running
|
@@ -98,6 +100,8 @@ class ClientSocket:
|
|
98
100
|
self.running = False
|
99
101
|
self.thread.join(timeout)
|
100
102
|
self.thread.kill()
|
103
|
+
os.close(self.get_signal)
|
104
|
+
os.close(self.set_signal)
|
101
105
|
|
102
106
|
def require_connection(self, timeout):
|
103
107
|
if self.connected:
|
@@ -111,7 +115,9 @@ class ClientSocket:
|
|
111
115
|
recvbuf = buffers.RecvBuffer(maxsize=self.options.max_msg_size)
|
112
116
|
sock = None
|
113
117
|
poll = select.poll()
|
118
|
+
poll.register(self.get_signal, select.POLLIN)
|
114
119
|
isconn = False # Local mirror of self.isconn without the lock.
|
120
|
+
writing = False
|
115
121
|
|
116
122
|
while self.running or (self.sendq and isconn):
|
117
123
|
|
@@ -121,7 +127,7 @@ class ClientSocket:
|
|
121
127
|
sock = self._connect()
|
122
128
|
if not sock:
|
123
129
|
break
|
124
|
-
poll.register(sock, select.POLLIN
|
130
|
+
poll.register(sock, select.POLLIN)
|
125
131
|
self.isconn.set()
|
126
132
|
isconn = True
|
127
133
|
if not self.options.autoconn:
|
@@ -130,33 +136,37 @@ class ClientSocket:
|
|
130
136
|
|
131
137
|
try:
|
132
138
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
_, mask = pairs[0]
|
139
|
-
|
140
|
-
if mask & select.POLLIN:
|
141
|
-
try:
|
142
|
-
recvbuf.recv(sock)
|
143
|
-
if recvbuf.done():
|
144
|
-
if self.recvq.qsize() > self.options.max_recv_queue:
|
145
|
-
raise RuntimeError('Too many incoming messages enqueued')
|
146
|
-
msg = recvbuf.result()
|
147
|
-
self.recvq.put(msg)
|
148
|
-
[x(msg) for x in self.callbacks_recv]
|
149
|
-
recvbuf = buffers.RecvBuffer(maxsize=self.options.max_msg_size)
|
150
|
-
except BlockingIOError:
|
151
|
-
pass
|
139
|
+
if not writing:
|
140
|
+
fds = [fd for fd, _ in poll.poll(0.2)]
|
141
|
+
if self.get_signal in fds:
|
142
|
+
writing = True
|
143
|
+
os.read(self.get_signal, 1)
|
152
144
|
|
153
|
-
|
145
|
+
try:
|
146
|
+
recvbuf.recv(sock)
|
147
|
+
if recvbuf.done():
|
148
|
+
if self.recvq.qsize() > self.options.max_recv_queue:
|
149
|
+
raise RuntimeError('Too many incoming messages enqueued')
|
150
|
+
msg = recvbuf.result()
|
151
|
+
self.recvq.put(msg)
|
152
|
+
[x(msg) for x in self.callbacks_recv]
|
153
|
+
recvbuf = buffers.RecvBuffer(maxsize=self.options.max_msg_size)
|
154
|
+
except BlockingIOError:
|
155
|
+
pass
|
156
|
+
|
157
|
+
if self.sendq:
|
154
158
|
try:
|
155
159
|
self.sendq[0].send(sock)
|
156
160
|
if self.sendq[0].done():
|
157
161
|
self.sendq.popleft()
|
162
|
+
if not self.sendq:
|
163
|
+
writing = False
|
158
164
|
except BlockingIOError:
|
159
165
|
pass
|
166
|
+
except ConnectionResetError:
|
167
|
+
# The server is gone but we may have buffered messages left to
|
168
|
+
# read, so we keep the socket open until recv() fails.
|
169
|
+
pass
|
160
170
|
|
161
171
|
except OSError as e:
|
162
172
|
detail = f'{type(e).__name__}'
|
@@ -176,9 +186,6 @@ class ClientSocket:
|
|
176
186
|
[x() for x in self.callbacks_disc]
|
177
187
|
continue
|
178
188
|
|
179
|
-
if self.options.loop_sleep:
|
180
|
-
time.sleep(self.options.loop_sleep)
|
181
|
-
|
182
189
|
if sock:
|
183
190
|
sock.close()
|
184
191
|
|
@@ -1,9 +1,9 @@
|
|
1
1
|
import collections
|
2
2
|
import dataclasses
|
3
|
+
import os
|
3
4
|
import queue
|
4
5
|
import selectors
|
5
6
|
import socket
|
6
|
-
import time
|
7
7
|
|
8
8
|
from . import buffers
|
9
9
|
from . import contextlib
|
@@ -32,7 +32,6 @@ class Options:
|
|
32
32
|
max_send_queue: int = 4096
|
33
33
|
logging: bool = True
|
34
34
|
logging_color: str = 'blue'
|
35
|
-
loop_sleep: float = 0.0
|
36
35
|
|
37
36
|
|
38
37
|
class ServerSocket:
|
@@ -56,7 +55,9 @@ class ServerSocket:
|
|
56
55
|
self.sock.bind(self.addr)
|
57
56
|
self.sock.setblocking(False)
|
58
57
|
self.sock.listen(8192)
|
58
|
+
self.get_signal, self.set_signal = os.pipe()
|
59
59
|
self.sel = selectors.DefaultSelector()
|
60
|
+
self.sel.register(self.get_signal, selectors.EVENT_READ, data='signal')
|
60
61
|
self.sel.register(self.sock, selectors.EVENT_READ, data=None)
|
61
62
|
self._log(f'Listening at {self.addr[0]}:{self.addr[1]}')
|
62
63
|
self.conns = {}
|
@@ -89,6 +90,7 @@ class ServerSocket:
|
|
89
90
|
try:
|
90
91
|
self.conns[addr].sendbufs.append(
|
91
92
|
buffers.SendBuffer(*data, maxsize=maxsize))
|
93
|
+
os.write(self.set_signal, bytes(1))
|
92
94
|
except KeyError:
|
93
95
|
self._log('Dropping message to disconnected client')
|
94
96
|
|
@@ -101,36 +103,38 @@ class ServerSocket:
|
|
101
103
|
[conn.sock.close() for conn in self.conns.values()]
|
102
104
|
self.sock.close()
|
103
105
|
self.sel.close()
|
106
|
+
os.close(self.get_signal)
|
107
|
+
os.close(self.set_signal)
|
104
108
|
|
105
109
|
def _loop(self):
|
110
|
+
writing = False
|
106
111
|
try:
|
107
112
|
while self.running or self._numsending():
|
108
|
-
writeable = []
|
109
|
-
# TODO: According to the py-spy profiler, the GIL is held during
|
110
|
-
# polling. Is there a way to avoid that?
|
111
113
|
for key, mask in self.sel.select(timeout=0.2):
|
112
|
-
if key.data
|
114
|
+
if key.data == 'signal':
|
115
|
+
writing = True
|
116
|
+
os.read(self.get_signal, 1)
|
117
|
+
elif key.data is None and self.reading:
|
113
118
|
assert mask & selectors.EVENT_READ
|
114
119
|
self._accept(key.fileobj)
|
115
120
|
elif mask & selectors.EVENT_READ and self.reading:
|
116
121
|
self._recv(key.data)
|
117
|
-
|
118
|
-
|
119
|
-
for conn in
|
120
|
-
|
121
|
-
continue
|
122
|
+
if not writing:
|
123
|
+
continue
|
124
|
+
pending = [conn for conn in self.conns.values() if conn.sendbufs]
|
125
|
+
for conn in pending:
|
122
126
|
try:
|
123
127
|
conn.sendbufs[0].send(conn.sock)
|
124
128
|
if conn.sendbufs[0].done():
|
125
129
|
conn.sendbufs.popleft()
|
130
|
+
if not any(conn.sendbufs for conn in pending):
|
131
|
+
writing = False
|
126
132
|
except BlockingIOError:
|
127
133
|
pass
|
128
134
|
except ConnectionResetError:
|
129
|
-
# The client is gone but we may have buffered messages left
|
130
|
-
# read, so we keep the socket open until recv() fails.
|
135
|
+
# The client is gone but we may have buffered messages left
|
136
|
+
# to read, so we keep the socket open until recv() fails.
|
131
137
|
pass
|
132
|
-
if self.options.loop_sleep:
|
133
|
-
time.sleep(self.options.loop_sleep)
|
134
138
|
except Exception as e:
|
135
139
|
self.error = e
|
136
140
|
|
@@ -139,8 +143,7 @@ class ServerSocket:
|
|
139
143
|
self._log(f'Accepted connection from {addr[0]}:{addr[1]}')
|
140
144
|
sock.setblocking(False)
|
141
145
|
conn = Connection(sock, addr)
|
142
|
-
self.sel.register(
|
143
|
-
sock, selectors.EVENT_READ | selectors.EVENT_WRITE, data=conn)
|
146
|
+
self.sel.register(sock, selectors.EVENT_READ, data=conn)
|
144
147
|
self.conns[addr] = conn
|
145
148
|
|
146
149
|
def _recv(self, conn):
|
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
|
File without changes
|
File without changes
|
File without changes
|