-
Notifications
You must be signed in to change notification settings - Fork 37
Fix transaction on_close and Java and Python block on close() #789
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Fix transaction on_close and Java and Python block on close() #789
Conversation
* under the License. | ||
*/ | ||
|
||
%module(threads=1) typedb_driver |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Key point! Since we're calling transaction close callbacks from another thread, we need this to avoid segfaults (not sure why - perhaps SWIG manages the GIL correctly for Python in this case!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:O
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How did it work in 2.x? Or did it?..
c/typedb_driver.i
Outdated
virtual ~TransactionCallbackDirector() {} | ||
virtual void callback(Error*) = 0; | ||
virtual ~TransactionCallbackDirector() { | ||
std::cout << "Callback::~Callback()" << std:: endl; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
helpful in case things aren't working as expected
) | ||
|
||
py_test( | ||
name = "test_driver", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good place to leave future integration tests
if (nativeObject.isOwned()) { | ||
try { | ||
transaction_force_close(nativeObject); | ||
transaction_force_close(nativeObject).get(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Java: force resolve close(). We could also let the user resolve it themselves?
) | ||
|
||
typedb_java_test( | ||
name = "test-driver", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new place to put java specific integration tests
impl Drop for TransactionTransmitter { | ||
fn drop(&mut self) { | ||
self.force_close(); | ||
// TODO: in the async context, this now returns a promise... do we care? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Big open question @dmitrii-ubskii !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok: looks like the typical recommended thing to do is make it fire-and-forget. So basically users can call .force_close()
if they want to await a promise first, then they can drop it if they want to be super sure in Rust.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Java/Python only use force_close() so they're blocking in GC anyway
|
||
pub(in crate::connection) fn force_close(&self) { | ||
#[cfg(not(feature = "sync"))] | ||
pub(in crate::connection) fn force_close(&self) -> impl Promise<'static, Result<()>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please validate these two functions!
let (sender, mut sink) = unbounded_async(); | ||
self.on_close_register_sink.send((Box::new(callback), sender)).ok(); | ||
sink.blocking_recv().expect("Did not receive on_close registration success signal"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we now don't return to the user unti the on_close is actually registered
move || Self::sync_dispatch_loop(queue_source, request_sink, collector, shutdown_signal) | ||
}); | ||
tokio::spawn(Self::listen_loop(response_source, collector, shutdown_sink)); | ||
tokio::spawn(Self::async_listen_loop(response_source, collector, on_close_callback_source, shutdown_sink)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we just have to get the on_close_callback into the Collector in some way, we can choose whether we lsiten to the channel in the async listen loop or the sync dispatch loop. To minimize waits, we're going to let Tokio manage it in the Async loop.
pub(crate) fn force_close(&self) -> impl Promise<'static, Result<()>> { | ||
self.transaction_transmitter.force_close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: in commit and rollback, we do something this:
let promise = self.single(TransactionRequest::Commit);
promisify! { require_transaction_response!(resolve!(promise), Rollback)
Do we want to do anything like this here?
rust/tests/integration/driver.rs
Outdated
} | ||
})); | ||
|
||
drop(transaction); // TODO: drop isn't blocking... so we need to spin? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drop question comes in here!
WIP Finally fixed on_close callback - threading issue Convert close to promise and resolve it in Python and Java Add Java onclose callback test Cleanup test Add mutex
8fb5f1e
to
e907782
Compare
"@typedb_bazel_distribution//platform:is_linux_x86_64": "@typedb_artifact_linux-x86_64//file", | ||
"@typedb_bazel_distribution//platform:is_mac_arm64": "@typedb_artifact_mac-arm64//file", | ||
"@typedb_bazel_distribution//platform:is_mac_x86_64": "@typedb_artifact_mac-x86_64//file", | ||
# "@typedb_bazel_distribution//platform:is_windows_x86_64": "@typedb_artifact_windows-x86_64//file", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be uncommented already
Usage and product changes
We notice that calling
transaction.close()
does not wait until the server has freed up resource. This makes quick sequences, such as tests where transactions open and are followed by database deletes, unreliable. Further investigation that workarounds using the existingon_close
callbacks in Python and Java caused segfaults. We fix both:Transaction.close()
in Python and Java now blocks for 1 round trip. We have the option to convert this fully into a promise return value the user can optionally resolve. We should pick the most relevant default!.on_close
callbacks to transactions.Implementation
Fix and enhance on_close callbacks:
i
layer!Make
close()
a blocking operation in Java and Python, which awaits a signal from the server that the transaction is actually closed and the resources are freed up.We add on_close callback integration tests for Python, Java, and Rust