valkey-glide 2.0.0rc3__cp310-cp310-macosx_11_0_arm64.whl → 2.2.1rc3__cp310-cp310-macosx_11_0_arm64.whl
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.
- glide/__init__.py +160 -106
- glide/async_commands/cluster_commands.py +108 -105
- glide/async_commands/core.py +637 -444
- glide/async_commands/{server_modules/ft.py → ft.py} +8 -7
- glide/async_commands/{server_modules/glide_json.py → glide_json.py} +15 -92
- glide/async_commands/standalone_commands.py +27 -58
- glide/glide.cpython-310-darwin.so +0 -0
- glide/glide.pyi +26 -1
- glide/glide_client.py +269 -125
- glide/logger.py +33 -21
- glide/opentelemetry.py +185 -0
- glide_shared/__init__.py +330 -0
- glide_shared/commands/__init__.py +0 -0
- {glide/async_commands → glide_shared/commands}/batch.py +476 -64
- glide_shared/commands/batch_options.py +261 -0
- glide_shared/commands/core_options.py +407 -0
- {glide/async_commands → glide_shared/commands}/server_modules/ft_options/ft_aggregate_options.py +3 -3
- {glide/async_commands → glide_shared/commands}/server_modules/ft_options/ft_create_options.py +4 -2
- {glide/async_commands → glide_shared/commands}/server_modules/ft_options/ft_profile_options.py +4 -4
- {glide/async_commands → glide_shared/commands}/server_modules/ft_options/ft_search_options.py +4 -2
- {glide/async_commands → glide_shared/commands}/server_modules/json_batch.py +4 -4
- glide_shared/commands/server_modules/json_options.py +93 -0
- {glide/async_commands → glide_shared/commands}/sorted_set.py +2 -2
- {glide/async_commands → glide_shared/commands}/stream.py +1 -1
- {glide → glide_shared}/config.py +386 -61
- {glide → glide_shared}/constants.py +3 -3
- {glide → glide_shared}/exceptions.py +27 -1
- glide_shared/protobuf/command_request_pb2.py +56 -0
- glide_shared/protobuf/connection_request_pb2.py +56 -0
- {glide → glide_shared}/protobuf/response_pb2.py +6 -6
- {glide → glide_shared}/routes.py +54 -15
- valkey_glide-2.2.1rc3.dist-info/METADATA +210 -0
- valkey_glide-2.2.1rc3.dist-info/RECORD +40 -0
- glide/protobuf/command_request_pb2.py +0 -54
- glide/protobuf/command_request_pb2.pyi +0 -1187
- glide/protobuf/connection_request_pb2.py +0 -54
- glide/protobuf/connection_request_pb2.pyi +0 -320
- glide/protobuf/response_pb2.pyi +0 -100
- valkey_glide-2.0.0rc3.dist-info/METADATA +0 -127
- valkey_glide-2.0.0rc3.dist-info/RECORD +0 -37
- {glide/async_commands → glide_shared/commands}/bitmap.py +0 -0
- {glide/async_commands → glide_shared/commands}/command_args.py +0 -0
- {glide/async_commands → glide_shared/commands}/server_modules/ft_options/ft_constants.py +0 -0
- {glide → glide_shared}/protobuf_codec.py +0 -0
- {valkey_glide-2.0.0rc3.dist-info → valkey_glide-2.2.1rc3.dist-info}/WHEEL +0 -0
glide/glide_client.py
CHANGED
|
@@ -1,62 +1,121 @@
|
|
|
1
1
|
# Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
3
|
import sys
|
|
5
4
|
import threading
|
|
6
|
-
from typing import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
ConnectionError,
|
|
18
|
-
ExecAbortError,
|
|
19
|
-
RequestError,
|
|
20
|
-
TimeoutError,
|
|
5
|
+
from typing import (
|
|
6
|
+
TYPE_CHECKING,
|
|
7
|
+
Any,
|
|
8
|
+
Awaitable,
|
|
9
|
+
Dict,
|
|
10
|
+
List,
|
|
11
|
+
Optional,
|
|
12
|
+
Set,
|
|
13
|
+
Tuple,
|
|
14
|
+
Union,
|
|
15
|
+
cast,
|
|
21
16
|
)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
from
|
|
26
|
-
from glide.
|
|
27
|
-
from glide.protobuf_codec import PartialMessageException, ProtobufCodec
|
|
28
|
-
from glide.routes import Route, set_protobuf_route
|
|
29
|
-
|
|
30
|
-
from .glide import (
|
|
17
|
+
|
|
18
|
+
import anyio
|
|
19
|
+
import sniffio
|
|
20
|
+
from anyio import to_thread
|
|
21
|
+
from glide.glide import (
|
|
31
22
|
DEFAULT_TIMEOUT_IN_MILLISECONDS,
|
|
32
23
|
MAX_REQUEST_ARGS_LEN,
|
|
33
24
|
ClusterScanCursor,
|
|
34
25
|
create_leaked_bytes_vec,
|
|
26
|
+
create_otel_span,
|
|
27
|
+
drop_otel_span,
|
|
35
28
|
get_statistics,
|
|
36
29
|
start_socket_listener_external,
|
|
37
30
|
value_from_pointer,
|
|
38
31
|
)
|
|
32
|
+
from glide_shared.commands.command_args import ObjectType
|
|
33
|
+
from glide_shared.commands.core_options import PubSubMsg
|
|
34
|
+
from glide_shared.config import BaseClientConfiguration, ServerCredentials
|
|
35
|
+
from glide_shared.constants import (
|
|
36
|
+
DEFAULT_READ_BYTES_SIZE,
|
|
37
|
+
OK,
|
|
38
|
+
TEncodable,
|
|
39
|
+
TRequest,
|
|
40
|
+
TResult,
|
|
41
|
+
)
|
|
42
|
+
from glide_shared.exceptions import (
|
|
43
|
+
ClosingError,
|
|
44
|
+
ConfigurationError,
|
|
45
|
+
ConnectionError,
|
|
46
|
+
get_request_error_class,
|
|
47
|
+
)
|
|
48
|
+
from glide_shared.protobuf.command_request_pb2 import (
|
|
49
|
+
Command,
|
|
50
|
+
CommandRequest,
|
|
51
|
+
RefreshIamToken,
|
|
52
|
+
RequestType,
|
|
53
|
+
)
|
|
54
|
+
from glide_shared.protobuf.connection_request_pb2 import ConnectionRequest
|
|
55
|
+
from glide_shared.protobuf.response_pb2 import Response
|
|
56
|
+
from glide_shared.protobuf_codec import PartialMessageException, ProtobufCodec
|
|
57
|
+
from glide_shared.routes import Route, set_protobuf_route
|
|
58
|
+
|
|
59
|
+
from .async_commands.cluster_commands import ClusterCommands
|
|
60
|
+
from .async_commands.core import CoreCommands
|
|
61
|
+
from .async_commands.standalone_commands import StandaloneCommands
|
|
62
|
+
from .logger import Level as LogLevel
|
|
63
|
+
from .logger import Logger as ClientLogger
|
|
64
|
+
from .opentelemetry import OpenTelemetry
|
|
39
65
|
|
|
40
66
|
if sys.version_info >= (3, 11):
|
|
41
|
-
import asyncio as async_timeout
|
|
42
67
|
from typing import Self
|
|
43
68
|
else:
|
|
44
|
-
import async_timeout
|
|
45
69
|
from typing_extensions import Self
|
|
46
70
|
|
|
71
|
+
if TYPE_CHECKING:
|
|
72
|
+
import asyncio
|
|
73
|
+
|
|
74
|
+
import trio
|
|
75
|
+
|
|
76
|
+
TTask = Union[asyncio.Task[None], trio.lowlevel.Task]
|
|
77
|
+
TFuture = Union[asyncio.Future[Any], "_CompatFuture"]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class _CompatFuture:
|
|
81
|
+
"""anyio shim for asyncio.Future-like functionality"""
|
|
82
|
+
|
|
83
|
+
def __init__(self) -> None:
|
|
84
|
+
self._is_done = anyio.Event()
|
|
85
|
+
self._result: Any = None
|
|
86
|
+
self._exception: Optional[Exception] = None
|
|
87
|
+
|
|
88
|
+
def set_result(self, result: Any) -> None:
|
|
89
|
+
self._result = result
|
|
90
|
+
self._is_done.set()
|
|
91
|
+
|
|
92
|
+
def set_exception(self, exception: Exception) -> None:
|
|
93
|
+
self._exception = exception
|
|
94
|
+
self._is_done.set()
|
|
95
|
+
|
|
96
|
+
def done(self) -> bool:
|
|
97
|
+
return self._is_done.is_set()
|
|
98
|
+
|
|
99
|
+
def __await__(self):
|
|
100
|
+
return self._is_done.wait().__await__()
|
|
47
101
|
|
|
48
|
-
def
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
102
|
+
def result(self) -> Any:
|
|
103
|
+
if self._exception:
|
|
104
|
+
raise self._exception
|
|
105
|
+
|
|
106
|
+
return self._result
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _get_new_future_instance() -> "TFuture":
|
|
110
|
+
if sniffio.current_async_library() == "asyncio":
|
|
111
|
+
import asyncio
|
|
112
|
+
|
|
113
|
+
return asyncio.get_running_loop().create_future()
|
|
114
|
+
|
|
115
|
+
# _CompatFuture is also compatible with asyncio, but is not as closely integrated
|
|
116
|
+
# into the asyncio event loop and thus introduces a noticeable performance
|
|
117
|
+
# degradation. so we only use it for trio
|
|
118
|
+
return _CompatFuture()
|
|
60
119
|
|
|
61
120
|
|
|
62
121
|
class BaseClient(CoreCommands):
|
|
@@ -65,17 +124,47 @@ class BaseClient(CoreCommands):
|
|
|
65
124
|
To create a new client, use the `create` classmethod
|
|
66
125
|
"""
|
|
67
126
|
self.config: BaseClientConfiguration = config
|
|
68
|
-
self._available_futures: Dict[int,
|
|
127
|
+
self._available_futures: Dict[int, "TFuture"] = {}
|
|
69
128
|
self._available_callback_indexes: List[int] = list()
|
|
70
129
|
self._buffered_requests: List[TRequest] = list()
|
|
71
130
|
self._writer_lock = threading.Lock()
|
|
72
131
|
self.socket_path: Optional[str] = None
|
|
73
|
-
self._reader_task: Optional[
|
|
132
|
+
self._reader_task: Optional["TTask"] = None
|
|
74
133
|
self._is_closed: bool = False
|
|
75
|
-
self._pubsub_futures: List[
|
|
134
|
+
self._pubsub_futures: List["TFuture"] = []
|
|
76
135
|
self._pubsub_lock = threading.Lock()
|
|
77
136
|
self._pending_push_notifications: List[Response] = list()
|
|
78
137
|
|
|
138
|
+
self._pending_tasks: Optional[Set[Awaitable[None]]] = None
|
|
139
|
+
"""asyncio-only to avoid gc on pending write tasks"""
|
|
140
|
+
|
|
141
|
+
def _create_task(self, task, *args, **kwargs):
|
|
142
|
+
"""framework agnostic free-floating task shim"""
|
|
143
|
+
framework = sniffio.current_async_library()
|
|
144
|
+
if framework == "trio":
|
|
145
|
+
from functools import partial
|
|
146
|
+
|
|
147
|
+
import trio
|
|
148
|
+
|
|
149
|
+
return trio.lowlevel.spawn_system_task(partial(task, **kwargs), *args)
|
|
150
|
+
elif framework == "asyncio":
|
|
151
|
+
import asyncio
|
|
152
|
+
|
|
153
|
+
# the asyncio event loop holds weak refs to tasks, so it's recommended to
|
|
154
|
+
# hold strong refs to them during their lifetime to prevent garbage
|
|
155
|
+
# collection
|
|
156
|
+
t = asyncio.create_task(task(*args, **kwargs))
|
|
157
|
+
|
|
158
|
+
if self._pending_tasks is None:
|
|
159
|
+
self._pending_tasks = set()
|
|
160
|
+
|
|
161
|
+
self._pending_tasks.add(t)
|
|
162
|
+
t.add_done_callback(self._pending_tasks.discard)
|
|
163
|
+
|
|
164
|
+
return t
|
|
165
|
+
|
|
166
|
+
raise RuntimeError(f"Unsupported async framework {framework}")
|
|
167
|
+
|
|
79
168
|
@classmethod
|
|
80
169
|
async def create(cls, config: BaseClientConfiguration) -> Self:
|
|
81
170
|
"""Creates a Glide client.
|
|
@@ -148,8 +237,8 @@ class BaseClient(CoreCommands):
|
|
|
148
237
|
"""
|
|
149
238
|
config = config
|
|
150
239
|
self = cls(config)
|
|
151
|
-
|
|
152
|
-
|
|
240
|
+
|
|
241
|
+
init_event: threading.Event = threading.Event()
|
|
153
242
|
|
|
154
243
|
def init_callback(socket_path: Optional[str], err: Optional[str]):
|
|
155
244
|
if err is not None:
|
|
@@ -161,7 +250,7 @@ class BaseClient(CoreCommands):
|
|
|
161
250
|
else:
|
|
162
251
|
# Received socket path
|
|
163
252
|
self.socket_path = socket_path
|
|
164
|
-
|
|
253
|
+
init_event.set()
|
|
165
254
|
|
|
166
255
|
start_socket_listener_external(init_callback=init_callback)
|
|
167
256
|
|
|
@@ -169,36 +258,27 @@ class BaseClient(CoreCommands):
|
|
|
169
258
|
# level or higher
|
|
170
259
|
ClientLogger.log(LogLevel.INFO, "connection info", "new connection established")
|
|
171
260
|
# Wait for the socket listener to complete its initialization
|
|
172
|
-
await
|
|
261
|
+
await to_thread.run_sync(init_event.wait)
|
|
173
262
|
# Create UDS connection
|
|
174
263
|
await self._create_uds_connection()
|
|
264
|
+
|
|
175
265
|
# Start the reader loop as a background task
|
|
176
|
-
self._reader_task =
|
|
266
|
+
self._reader_task = self._create_task(self._reader_loop)
|
|
267
|
+
|
|
177
268
|
# Set the client configurations
|
|
178
269
|
await self._set_connection_configurations()
|
|
270
|
+
|
|
179
271
|
return self
|
|
180
272
|
|
|
181
273
|
async def _create_uds_connection(self) -> None:
|
|
182
274
|
try:
|
|
183
275
|
# Open an UDS connection
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
path=self.socket_path
|
|
276
|
+
with anyio.fail_after(DEFAULT_TIMEOUT_IN_MILLISECONDS):
|
|
277
|
+
self._stream = await anyio.connect_unix(
|
|
278
|
+
path=cast(str, self.socket_path)
|
|
187
279
|
)
|
|
188
|
-
self._reader = reader
|
|
189
|
-
self._writer = writer
|
|
190
280
|
except Exception as e:
|
|
191
|
-
|
|
192
|
-
raise
|
|
193
|
-
|
|
194
|
-
def __del__(self) -> None:
|
|
195
|
-
try:
|
|
196
|
-
if self._reader_task:
|
|
197
|
-
self._reader_task.cancel()
|
|
198
|
-
except RuntimeError as e:
|
|
199
|
-
if "no running event loop" in str(e):
|
|
200
|
-
# event loop already closed
|
|
201
|
-
pass
|
|
281
|
+
raise ClosingError("Failed to create UDS connection") from e
|
|
202
282
|
|
|
203
283
|
async def close(self, err_message: Optional[str] = None) -> None:
|
|
204
284
|
"""
|
|
@@ -210,25 +290,24 @@ class BaseClient(CoreCommands):
|
|
|
210
290
|
closing all open futures.
|
|
211
291
|
Defaults to None.
|
|
212
292
|
"""
|
|
213
|
-
self._is_closed
|
|
214
|
-
|
|
215
|
-
if
|
|
216
|
-
|
|
217
|
-
response_future.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
pubsub_future.
|
|
223
|
-
|
|
224
|
-
|
|
293
|
+
if not self._is_closed:
|
|
294
|
+
self._is_closed = True
|
|
295
|
+
err_message = "" if err_message is None else err_message
|
|
296
|
+
for response_future in self._available_futures.values():
|
|
297
|
+
if not response_future.done():
|
|
298
|
+
response_future.set_exception(ClosingError(err_message))
|
|
299
|
+
try:
|
|
300
|
+
self._pubsub_lock.acquire()
|
|
301
|
+
for pubsub_future in self._pubsub_futures:
|
|
302
|
+
if not pubsub_future.done():
|
|
303
|
+
pubsub_future.set_exception(ClosingError(err_message))
|
|
304
|
+
finally:
|
|
305
|
+
self._pubsub_lock.release()
|
|
225
306
|
|
|
226
|
-
|
|
227
|
-
await self._writer.wait_closed()
|
|
228
|
-
self.__del__()
|
|
307
|
+
await self._stream.aclose()
|
|
229
308
|
|
|
230
|
-
def _get_future(self, callback_idx: int) ->
|
|
231
|
-
response_future:
|
|
309
|
+
def _get_future(self, callback_idx: int) -> "TFuture":
|
|
310
|
+
response_future: "TFuture" = _get_new_future_instance()
|
|
232
311
|
self._available_futures.update({callback_idx: response_future})
|
|
233
312
|
return response_future
|
|
234
313
|
|
|
@@ -237,14 +316,15 @@ class BaseClient(CoreCommands):
|
|
|
237
316
|
|
|
238
317
|
async def _set_connection_configurations(self) -> None:
|
|
239
318
|
conn_request = self._get_protobuf_conn_request()
|
|
240
|
-
response_future:
|
|
241
|
-
|
|
319
|
+
response_future: "TFuture" = self._get_future(0)
|
|
320
|
+
self._create_write_task(conn_request)
|
|
242
321
|
await response_future
|
|
243
|
-
|
|
244
|
-
|
|
322
|
+
res = response_future.result()
|
|
323
|
+
if res is not OK:
|
|
324
|
+
raise ClosingError(res)
|
|
245
325
|
|
|
246
326
|
def _create_write_task(self, request: TRequest):
|
|
247
|
-
|
|
327
|
+
self._create_task(self._write_or_buffer_request, request)
|
|
248
328
|
|
|
249
329
|
async def _write_or_buffer_request(self, request: TRequest):
|
|
250
330
|
self._buffered_requests.append(request)
|
|
@@ -252,7 +332,21 @@ class BaseClient(CoreCommands):
|
|
|
252
332
|
try:
|
|
253
333
|
while len(self._buffered_requests) > 0:
|
|
254
334
|
await self._write_buffered_requests_to_socket()
|
|
255
|
-
|
|
335
|
+
except Exception as e:
|
|
336
|
+
# trio system tasks cannot raise exceptions, so gracefully propagate
|
|
337
|
+
# any error to the pending future instead
|
|
338
|
+
callback_idx = (
|
|
339
|
+
request.callback_idx if isinstance(request, CommandRequest) else 0
|
|
340
|
+
)
|
|
341
|
+
res_future = self._available_futures.pop(callback_idx, None)
|
|
342
|
+
if res_future and not res_future.done():
|
|
343
|
+
res_future.set_exception(e)
|
|
344
|
+
else:
|
|
345
|
+
ClientLogger.log(
|
|
346
|
+
LogLevel.WARN,
|
|
347
|
+
"unhandled response error",
|
|
348
|
+
f"Unhandled response error for unknown request: {callback_idx}",
|
|
349
|
+
)
|
|
256
350
|
finally:
|
|
257
351
|
self._writer_lock.release()
|
|
258
352
|
|
|
@@ -262,8 +356,10 @@ class BaseClient(CoreCommands):
|
|
|
262
356
|
b_arr = bytearray()
|
|
263
357
|
for request in requests:
|
|
264
358
|
ProtobufCodec.encode_delimited(b_arr, request)
|
|
265
|
-
|
|
266
|
-
|
|
359
|
+
try:
|
|
360
|
+
await self._stream.send(b_arr)
|
|
361
|
+
except (anyio.ClosedResourceError, anyio.EndOfStream):
|
|
362
|
+
raise ClosingError("The communication layer was unexpectedly closed.")
|
|
267
363
|
|
|
268
364
|
def _encode_arg(self, arg: TEncodable) -> bytes:
|
|
269
365
|
"""
|
|
@@ -314,6 +410,13 @@ class BaseClient(CoreCommands):
|
|
|
314
410
|
raise ClosingError(
|
|
315
411
|
"Unable to execute requests; the client is closed. Please create a new client."
|
|
316
412
|
)
|
|
413
|
+
|
|
414
|
+
# Create span if OpenTelemetry is configured and sampling indicates we should trace
|
|
415
|
+
span = None
|
|
416
|
+
if OpenTelemetry.should_sample():
|
|
417
|
+
command_name = RequestType.Name(request_type)
|
|
418
|
+
span = create_otel_span(command_name)
|
|
419
|
+
|
|
317
420
|
request = CommandRequest()
|
|
318
421
|
request.callback_idx = self._get_callback_index()
|
|
319
422
|
request.single_command.request_type = request_type
|
|
@@ -328,6 +431,11 @@ class BaseClient(CoreCommands):
|
|
|
328
431
|
request.single_command.args_vec_pointer = create_leaked_bytes_vec(
|
|
329
432
|
encoded_args
|
|
330
433
|
)
|
|
434
|
+
|
|
435
|
+
# Add span pointer to request if span was created
|
|
436
|
+
if span:
|
|
437
|
+
request.root_span_ptr = span
|
|
438
|
+
|
|
331
439
|
set_protobuf_route(request, route)
|
|
332
440
|
return await self._write_request_await_response(request)
|
|
333
441
|
|
|
@@ -345,6 +453,14 @@ class BaseClient(CoreCommands):
|
|
|
345
453
|
raise ClosingError(
|
|
346
454
|
"Unable to execute requests; the client is closed. Please create a new client."
|
|
347
455
|
)
|
|
456
|
+
|
|
457
|
+
# Create span if OpenTelemetry is configured and sampling indicates we should trace
|
|
458
|
+
span = None
|
|
459
|
+
|
|
460
|
+
if OpenTelemetry.should_sample():
|
|
461
|
+
# Use "Batch" as span name for batches
|
|
462
|
+
span = create_otel_span("Batch")
|
|
463
|
+
|
|
348
464
|
request = CommandRequest()
|
|
349
465
|
request.callback_idx = self._get_callback_index()
|
|
350
466
|
batch_commands = []
|
|
@@ -366,6 +482,11 @@ class BaseClient(CoreCommands):
|
|
|
366
482
|
request.batch.timeout = timeout
|
|
367
483
|
request.batch.retry_server_error = retry_server_error
|
|
368
484
|
request.batch.retry_connection_error = retry_connection_error
|
|
485
|
+
|
|
486
|
+
# Add span pointer to request if span was created
|
|
487
|
+
if span:
|
|
488
|
+
request.root_span_ptr = span
|
|
489
|
+
|
|
369
490
|
set_protobuf_route(request, route)
|
|
370
491
|
return await self._write_request_await_response(request)
|
|
371
492
|
|
|
@@ -400,7 +521,7 @@ class BaseClient(CoreCommands):
|
|
|
400
521
|
set_protobuf_route(request, route)
|
|
401
522
|
return await self._write_request_await_response(request)
|
|
402
523
|
|
|
403
|
-
async def get_pubsub_message(self) ->
|
|
524
|
+
async def get_pubsub_message(self) -> PubSubMsg:
|
|
404
525
|
if self._is_closed:
|
|
405
526
|
raise ClosingError(
|
|
406
527
|
"Unable to execute requests; the client is closed. Please create a new client."
|
|
@@ -417,16 +538,17 @@ class BaseClient(CoreCommands):
|
|
|
417
538
|
)
|
|
418
539
|
|
|
419
540
|
# locking might not be required
|
|
420
|
-
response_future:
|
|
541
|
+
response_future: "TFuture" = _get_new_future_instance()
|
|
421
542
|
try:
|
|
422
543
|
self._pubsub_lock.acquire()
|
|
423
544
|
self._pubsub_futures.append(response_future)
|
|
424
545
|
self._complete_pubsub_futures_safe()
|
|
425
546
|
finally:
|
|
426
547
|
self._pubsub_lock.release()
|
|
427
|
-
|
|
548
|
+
await response_future
|
|
549
|
+
return response_future.result()
|
|
428
550
|
|
|
429
|
-
def try_get_pubsub_message(self) -> Optional[
|
|
551
|
+
def try_get_pubsub_message(self) -> Optional[PubSubMsg]:
|
|
430
552
|
if self._is_closed:
|
|
431
553
|
raise ClosingError(
|
|
432
554
|
"Unable to execute requests; the client is closed. Please create a new client."
|
|
@@ -443,7 +565,7 @@ class BaseClient(CoreCommands):
|
|
|
443
565
|
)
|
|
444
566
|
|
|
445
567
|
# locking might not be required
|
|
446
|
-
msg: Optional[
|
|
568
|
+
msg: Optional[PubSubMsg] = None
|
|
447
569
|
try:
|
|
448
570
|
self._pubsub_lock.acquire()
|
|
449
571
|
self._complete_pubsub_futures_safe()
|
|
@@ -457,12 +579,11 @@ class BaseClient(CoreCommands):
|
|
|
457
579
|
def _cancel_pubsub_futures_with_exception_safe(self, exception: ConnectionError):
|
|
458
580
|
while len(self._pubsub_futures):
|
|
459
581
|
next_future = self._pubsub_futures.pop(0)
|
|
460
|
-
|
|
461
|
-
next_future.set_exception(exception)
|
|
582
|
+
next_future.set_exception(exception)
|
|
462
583
|
|
|
463
584
|
def _notification_to_pubsub_message_safe(
|
|
464
585
|
self, response: Response
|
|
465
|
-
) -> Optional[
|
|
586
|
+
) -> Optional[PubSubMsg]:
|
|
466
587
|
pubsub_message = None
|
|
467
588
|
push_notification = cast(
|
|
468
589
|
Dict[str, Any], value_from_pointer(response.resp_pointer)
|
|
@@ -481,11 +602,11 @@ class BaseClient(CoreCommands):
|
|
|
481
602
|
):
|
|
482
603
|
values: List = push_notification["values"]
|
|
483
604
|
if message_kind == "PMessage":
|
|
484
|
-
pubsub_message =
|
|
605
|
+
pubsub_message = PubSubMsg(
|
|
485
606
|
message=values[2], channel=values[1], pattern=values[0]
|
|
486
607
|
)
|
|
487
608
|
else:
|
|
488
|
-
pubsub_message =
|
|
609
|
+
pubsub_message = PubSubMsg(
|
|
489
610
|
message=values[1], channel=values[0], pattern=None
|
|
490
611
|
)
|
|
491
612
|
elif (
|
|
@@ -538,10 +659,16 @@ class BaseClient(CoreCommands):
|
|
|
538
659
|
if response.HasField("closing_error")
|
|
539
660
|
else f"Client Error - closing due to unknown error. callback index: {response.callback_idx}"
|
|
540
661
|
)
|
|
662
|
+
exc = ClosingError(err_msg)
|
|
541
663
|
if res_future is not None:
|
|
542
|
-
res_future.set_exception(
|
|
543
|
-
|
|
544
|
-
|
|
664
|
+
res_future.set_exception(exc)
|
|
665
|
+
else:
|
|
666
|
+
ClientLogger.log(
|
|
667
|
+
LogLevel.WARN,
|
|
668
|
+
"unhandled response error",
|
|
669
|
+
f"Unhandled response error for unknown request: {response.callback_idx}",
|
|
670
|
+
)
|
|
671
|
+
raise exc
|
|
545
672
|
else:
|
|
546
673
|
self._available_callback_indexes.append(response.callback_idx)
|
|
547
674
|
if response.HasField("request_error"):
|
|
@@ -554,6 +681,10 @@ class BaseClient(CoreCommands):
|
|
|
554
681
|
else:
|
|
555
682
|
res_future.set_result(None)
|
|
556
683
|
|
|
684
|
+
# Clean up span if it was created
|
|
685
|
+
if response.HasField("root_span_ptr"):
|
|
686
|
+
drop_otel_span(response.root_span_ptr)
|
|
687
|
+
|
|
557
688
|
async def _process_push(self, response: Response) -> None:
|
|
558
689
|
if response.HasField("closing_error") or not response.HasField("resp_pointer"):
|
|
559
690
|
err_msg = (
|
|
@@ -561,9 +692,7 @@ class BaseClient(CoreCommands):
|
|
|
561
692
|
if response.HasField("closing_error")
|
|
562
693
|
else "Client Error - push notification without resp_pointer"
|
|
563
694
|
)
|
|
564
|
-
await self.close(err_msg)
|
|
565
695
|
raise ClosingError(err_msg)
|
|
566
|
-
|
|
567
696
|
try:
|
|
568
697
|
self._pubsub_lock.acquire()
|
|
569
698
|
callback, context = self.config._get_pubsub_callback_and_context()
|
|
@@ -579,30 +708,36 @@ class BaseClient(CoreCommands):
|
|
|
579
708
|
|
|
580
709
|
async def _reader_loop(self) -> None:
|
|
581
710
|
# Socket reader loop
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
if len(read_bytes) == 0:
|
|
586
|
-
err_msg = "The communication layer was unexpectedly closed."
|
|
587
|
-
await self.close(err_msg)
|
|
588
|
-
raise ClosingError(err_msg)
|
|
589
|
-
read_bytes = remaining_read_bytes + bytearray(read_bytes)
|
|
590
|
-
read_bytes_view = memoryview(read_bytes)
|
|
591
|
-
offset = 0
|
|
592
|
-
while offset <= len(read_bytes):
|
|
711
|
+
try:
|
|
712
|
+
remaining_read_bytes = bytearray()
|
|
713
|
+
while True:
|
|
593
714
|
try:
|
|
594
|
-
|
|
595
|
-
|
|
715
|
+
read_bytes = await self._stream.receive(DEFAULT_READ_BYTES_SIZE)
|
|
716
|
+
except (anyio.ClosedResourceError, anyio.EndOfStream):
|
|
717
|
+
raise ClosingError(
|
|
718
|
+
"The communication layer was unexpectedly closed."
|
|
596
719
|
)
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
720
|
+
read_bytes = remaining_read_bytes + bytearray(read_bytes)
|
|
721
|
+
read_bytes_view = memoryview(read_bytes)
|
|
722
|
+
offset = 0
|
|
723
|
+
while offset <= len(read_bytes):
|
|
724
|
+
try:
|
|
725
|
+
response, offset = ProtobufCodec.decode_delimited(
|
|
726
|
+
read_bytes, read_bytes_view, offset, Response
|
|
727
|
+
)
|
|
728
|
+
except PartialMessageException:
|
|
729
|
+
# Received only partial response, break the inner loop
|
|
730
|
+
remaining_read_bytes = read_bytes[offset:]
|
|
731
|
+
break
|
|
732
|
+
response = cast(Response, response)
|
|
733
|
+
if response.is_push:
|
|
734
|
+
await self._process_push(response=response)
|
|
735
|
+
else:
|
|
736
|
+
await self._process_response(response=response)
|
|
737
|
+
except Exception as e:
|
|
738
|
+
# close and stop reading at terminal exceptions from incoming responses or
|
|
739
|
+
# stream closures
|
|
740
|
+
await self.close(str(e))
|
|
606
741
|
|
|
607
742
|
async def get_statistics(self) -> dict:
|
|
608
743
|
return get_statistics()
|
|
@@ -623,6 +758,15 @@ class BaseClient(CoreCommands):
|
|
|
623
758
|
self.config.credentials.password = password or ""
|
|
624
759
|
return response
|
|
625
760
|
|
|
761
|
+
async def _refresh_iam_token(self) -> TResult:
|
|
762
|
+
request = CommandRequest()
|
|
763
|
+
request.callback_idx = self._get_callback_index()
|
|
764
|
+
request.refresh_iam_token.CopyFrom(
|
|
765
|
+
RefreshIamToken()
|
|
766
|
+
) # Empty message, just triggers the refresh
|
|
767
|
+
response = await self._write_request_await_response(request)
|
|
768
|
+
return response
|
|
769
|
+
|
|
626
770
|
|
|
627
771
|
class GlideClusterClient(BaseClient, ClusterCommands):
|
|
628
772
|
"""
|