portal 3.1.6__tar.gz → 3.1.8__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.
Files changed (35) hide show
  1. {portal-3.1.6/portal.egg-info → portal-3.1.8}/PKG-INFO +1 -1
  2. {portal-3.1.6 → portal-3.1.8}/portal/__init__.py +1 -1
  3. {portal-3.1.6 → portal-3.1.8}/portal/client.py +20 -12
  4. {portal-3.1.6 → portal-3.1.8}/portal/client_socket.py +12 -3
  5. {portal-3.1.6 → portal-3.1.8}/portal/contextlib.py +5 -3
  6. {portal-3.1.6 → portal-3.1.8}/portal/server_socket.py +2 -0
  7. {portal-3.1.6 → portal-3.1.8/portal.egg-info}/PKG-INFO +1 -1
  8. {portal-3.1.6 → portal-3.1.8}/tests/test_client.py +16 -2
  9. {portal-3.1.6 → portal-3.1.8}/LICENSE +0 -0
  10. {portal-3.1.6 → portal-3.1.8}/MANIFEST.in +0 -0
  11. {portal-3.1.6 → portal-3.1.8}/README.md +0 -0
  12. {portal-3.1.6 → portal-3.1.8}/portal/batching.py +0 -0
  13. {portal-3.1.6 → portal-3.1.8}/portal/buffers.py +0 -0
  14. {portal-3.1.6 → portal-3.1.8}/portal/packlib.py +0 -0
  15. {portal-3.1.6 → portal-3.1.8}/portal/poollib.py +0 -0
  16. {portal-3.1.6 → portal-3.1.8}/portal/process.py +0 -0
  17. {portal-3.1.6 → portal-3.1.8}/portal/server.py +0 -0
  18. {portal-3.1.6 → portal-3.1.8}/portal/sharray.py +0 -0
  19. {portal-3.1.6 → portal-3.1.8}/portal/thread.py +0 -0
  20. {portal-3.1.6 → portal-3.1.8}/portal/utils.py +0 -0
  21. {portal-3.1.6 → portal-3.1.8}/portal.egg-info/SOURCES.txt +0 -0
  22. {portal-3.1.6 → portal-3.1.8}/portal.egg-info/dependency_links.txt +0 -0
  23. {portal-3.1.6 → portal-3.1.8}/portal.egg-info/requires.txt +0 -0
  24. {portal-3.1.6 → portal-3.1.8}/portal.egg-info/top_level.txt +0 -0
  25. {portal-3.1.6 → portal-3.1.8}/pyproject.toml +0 -0
  26. {portal-3.1.6 → portal-3.1.8}/requirements.txt +0 -0
  27. {portal-3.1.6 → portal-3.1.8}/setup.cfg +0 -0
  28. {portal-3.1.6 → portal-3.1.8}/setup.py +0 -0
  29. {portal-3.1.6 → portal-3.1.8}/tests/test_batching.py +0 -0
  30. {portal-3.1.6 → portal-3.1.8}/tests/test_errfile.py +0 -0
  31. {portal-3.1.6 → portal-3.1.8}/tests/test_pack.py +0 -0
  32. {portal-3.1.6 → portal-3.1.8}/tests/test_process.py +0 -0
  33. {portal-3.1.6 → portal-3.1.8}/tests/test_server.py +0 -0
  34. {portal-3.1.6 → portal-3.1.8}/tests/test_socket.py +0 -0
  35. {portal-3.1.6 → portal-3.1.8}/tests/test_thread.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portal
3
- Version: 3.1.6
3
+ Version: 3.1.8
4
4
  Summary: Fast and reliable distributed systems in Python
5
5
  Home-page: http://github.com/danijar/portal
6
6
  Author: Danijar Hafner
@@ -1,4 +1,4 @@
1
- __version__ = '3.1.6'
1
+ __version__ = '3.1.8'
2
2
 
3
3
  import multiprocessing as mp
4
4
  try:
@@ -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
- self.socket.send(*sendargs)
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):
@@ -113,10 +121,10 @@ class Client:
113
121
 
114
122
  def _disc(self):
115
123
  if self.socket.options.autoconn:
116
- for future in self.futures.values():
124
+ for future in list(self.futures.values()):
117
125
  future.resend = True
118
126
  else:
119
- for future in self.futures.values():
127
+ for future in list(self.futures.values()):
120
128
  self._seterr(future, client_socket.Disconnected)
121
129
  self.futures.clear()
122
130
 
@@ -127,17 +135,17 @@ class Client:
127
135
  self.socket.send(*future.sendargs)
128
136
 
129
137
  def _seterr(self, future, e):
130
- raised = [False]
131
- future.raised = raised
132
138
  future.set_error(e)
139
+ rai = future.rai
133
140
  weakref.finalize(future, lambda: (
134
- None if raised[0] else self.errors.append(e)))
141
+ None if rai[0] else self.errors.append(e)))
135
142
 
136
143
 
137
144
  class Future:
138
145
 
139
- def __init__(self):
140
- self.raised = [False]
146
+ def __init__(self, rai):
147
+ assert rai == [False]
148
+ self.rai = rai
141
149
  self.con = threading.Condition()
142
150
  self.don = False
143
151
  self.res = None
@@ -147,7 +155,7 @@ class Future:
147
155
  if not self.done:
148
156
  return 'Future(done=False)'
149
157
  elif self.err:
150
- return f"Future(done=True, error='{self.err}', raised={self.raised[0]})"
158
+ return f"Future(done=True, error='{self.err}', raised={self.rai[0]})"
151
159
  else:
152
160
  return 'Future(done=True)'
153
161
 
@@ -165,8 +173,8 @@ class Future:
165
173
  assert self.don
166
174
  if self.err is None:
167
175
  return self.res
168
- if not self.raised[0]:
169
- self.raised[0] = True
176
+ if not self.rai[0]:
177
+ self.rai[0] = True
170
178
  raise self.err
171
179
 
172
180
  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
- readable, writable, _ = select.select([sock], [sock], [], 0.2)
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 readable:
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 writable:
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
- print(f'Shutting down due to error file: {self.errfile}')
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portal
3
- Version: 3.1.6
3
+ Version: 3.1.8
4
4
  Summary: Fast and reliable distributed systems in Python
5
5
  Home-page: http://github.com/danijar/portal
6
6
  Author: Danijar Hafner
@@ -48,8 +48,22 @@ class TestClient:
48
48
  client.connect()
49
49
  assert client.fn(1).result() == 1
50
50
  server.close()
51
- with pytest.raises(portal.Disconnected):
52
- client.fn(2).result()
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