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.
Files changed (35) hide show
  1. {portal-3.1.5/portal.egg-info → portal-3.1.7}/PKG-INFO +1 -1
  2. {portal-3.1.5 → portal-3.1.7}/portal/__init__.py +1 -1
  3. {portal-3.1.5 → portal-3.1.7}/portal/client.py +35 -12
  4. {portal-3.1.5 → portal-3.1.7}/portal/client_socket.py +12 -3
  5. {portal-3.1.5 → portal-3.1.7}/portal/contextlib.py +5 -3
  6. {portal-3.1.5 → portal-3.1.7}/portal/server_socket.py +2 -0
  7. {portal-3.1.5 → portal-3.1.7/portal.egg-info}/PKG-INFO +1 -1
  8. {portal-3.1.5 → portal-3.1.7}/tests/test_client.py +16 -2
  9. {portal-3.1.5 → portal-3.1.7}/LICENSE +0 -0
  10. {portal-3.1.5 → portal-3.1.7}/MANIFEST.in +0 -0
  11. {portal-3.1.5 → portal-3.1.7}/README.md +0 -0
  12. {portal-3.1.5 → portal-3.1.7}/portal/batching.py +0 -0
  13. {portal-3.1.5 → portal-3.1.7}/portal/buffers.py +0 -0
  14. {portal-3.1.5 → portal-3.1.7}/portal/packlib.py +0 -0
  15. {portal-3.1.5 → portal-3.1.7}/portal/poollib.py +0 -0
  16. {portal-3.1.5 → portal-3.1.7}/portal/process.py +0 -0
  17. {portal-3.1.5 → portal-3.1.7}/portal/server.py +0 -0
  18. {portal-3.1.5 → portal-3.1.7}/portal/sharray.py +0 -0
  19. {portal-3.1.5 → portal-3.1.7}/portal/thread.py +0 -0
  20. {portal-3.1.5 → portal-3.1.7}/portal/utils.py +0 -0
  21. {portal-3.1.5 → portal-3.1.7}/portal.egg-info/SOURCES.txt +0 -0
  22. {portal-3.1.5 → portal-3.1.7}/portal.egg-info/dependency_links.txt +0 -0
  23. {portal-3.1.5 → portal-3.1.7}/portal.egg-info/requires.txt +0 -0
  24. {portal-3.1.5 → portal-3.1.7}/portal.egg-info/top_level.txt +0 -0
  25. {portal-3.1.5 → portal-3.1.7}/pyproject.toml +0 -0
  26. {portal-3.1.5 → portal-3.1.7}/requirements.txt +0 -0
  27. {portal-3.1.5 → portal-3.1.7}/setup.cfg +0 -0
  28. {portal-3.1.5 → portal-3.1.7}/setup.py +0 -0
  29. {portal-3.1.5 → portal-3.1.7}/tests/test_batching.py +0 -0
  30. {portal-3.1.5 → portal-3.1.7}/tests/test_errfile.py +0 -0
  31. {portal-3.1.5 → portal-3.1.7}/tests/test_pack.py +0 -0
  32. {portal-3.1.5 → portal-3.1.7}/tests/test_process.py +0 -0
  33. {portal-3.1.5 → portal-3.1.7}/tests/test_server.py +0 -0
  34. {portal-3.1.5 → portal-3.1.7}/tests/test_socket.py +0 -0
  35. {portal-3.1.5 → portal-3.1.7}/tests/test_thread.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portal
3
- Version: 3.1.5
3
+ Version: 3.1.7
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.5'
1
+ __version__ = '3.1.7'
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):
@@ -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 raised[0] else self.errors.append(e)))
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
- self.raised = [False]
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.raised[0]})"
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.raised[0]:
169
- self.raised[0] = True
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
- 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.5
3
+ Version: 3.1.7
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