Concurrency & joining requests
radkit_client
is designed so that concurrent execution of operations is
possible without having to deal with threads or async code. These operations
will spawn background tasks without waiting for them. For instance, a
device.exec("show version")
will not wait for the exec command to complete
before returning something. Instead, we have an additional blocking .wait()
method on the returned object that can be called at the point that we’d like to
wait for the request to complete, so that we can capture the result.
request = device.exec("show version") # Not blocking.
request.wait() # Blocking.
print(request)
As long as no .wait()
method is called, all scheduled operations will be
performed concurrently.
The “joining” that is described in the following sections applies to any
radkit_client
object that has a .wait()
method.
Joining multiple requests/tasks (with context manager)
If we have multiple objects that represent a request or background job, then
it’s possible to join them into a single object so that we can wait for them
together or monitor them. This is possible with the
join()
context manager:
from radkit_client.sync.joining import join
# Do requests.
request1 = device1.exec("show version")
request2 = device2.exec("show version")
# Join requests.
with join(request1, request2) as joined_request:
# Show a progress bar for monitoring the completion of requests.
joined_request.show_progress()
# Single wait that waits for all underlying requests to complete.
joined_request.wait()
The JoinedTasks
object will display the
requests/tasks it contains with their status; exception message (if any) and
their representation:
[SUCCESS] <radkit_client.sync.joining.JoinedTasks object at 0xffffa49671f0>
Set of 2 joined tasks (2 completed successful, 0 completed with failure)
key status exception __repr__
----- -------- ----------- -------------------------------------------------------------------------------------
0 SUCCESS <radkit_client.async_.request.AsyncTransformedFillerRequest object at 0xffffa5025fa0>
1 SUCCESS <radkit_client.async_.request.AsyncTransformedFillerRequest object at 0xffffa44498b0>
The reason for this to be a context manager is because the
JoinedTasks
object will spawn background jobs to
monitor the contained objects. These background jobs will be cancelled at the
end of the context manager, so that these resources will get released.
Without context manager / in the REPL
It’s not convenient to use a context manager in the REPL. So, similar to all
the login function, we have another join()
method
that does not require to be opened like a context manager. This one has to be
imported from radkit_client.sync.context
or radkit_client.sync
(but
not radkit_client.sync.joining
).
from radkit_client.sync import join
joined_request = join(request1, request2)
joined_request.wait()
To use this notation in a standalone script, we have to create the context like this:
from radkit_client.sync import create_context, join
with create_context():
...
joined_request = join(request1, request2)
joined_request.wait()
Iterating over requests as they complete
The JoinedTasks
object has an
as_completed()
method which returns a
generator that yields the containing requests/tasks as they complete. This is
very useful when some operation when many requests were started and some
operation needs to be performed for each request when it completes.
for item in joined_request.as_completed():
print("Completed item:", item)
Objects that can be joined together
Every object that has a .wait()
method and .status
field can be joined.
This includes for instance:
A
Service
object and itsServiceCapabilities
object.An exec result, like
ExecCommandsResult
andExecSingleCommandResult
An HTTP response:
HttpResponse
A
SimpleRequest
.Terminal connection objects:
InteractiveConnection
,FileReadConnection
andFileWriteConnection
.