-
Notifications
You must be signed in to change notification settings - Fork 381
Wrap the Rust HTTP client with make_deferred_yieldable
#18903
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: develop
Are you sure you want to change the base?
Wrap the Rust HTTP client with make_deferred_yieldable
#18903
Conversation
So downstream usage doesn't need to use `PreserveLoggingContext()` or `make_deferred_yieldable` Spawning from #18870 and #18357 (comment)
from synapse.types import ISynapseReactor | ||
|
||
|
||
class HttpClient: |
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.
I think this solution is awful.
What's a better way we can wrap the Rust code to do the right thing?
Call Python from the Rust code?
Write an equivalent wrapper in Rust? (not sure how to approach)
Better way to organize all of this? I initially tried to rename http_client.pyi
to internal_http_client.pyi
and add a new http_client.py
with this wrapper all within synapse/synapse_rust/
but the native Python doesn't get picked up.
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.
I think calling make_deferred_yieldable
from the Rust side (and therefore not having the wrapper) would be a reasonable alternative, but I also don't think this wrapper approach is too bad — at least it's extremely clear. (Maybe a bit easy to forget about using it?)
If you want a sketch of calling Python code from Rust, here's how I called _get_size_of
for my caching fun, whilst only importing it once:
static GETSIZEOF: OnceLock<pyo3::Py<pyo3::PyAny>> = OnceLock::new();
fn get_size_of(py: Python<'_>, obj: &Bound<'_, PyAny>) -> u64 {
let getsizeof = GETSIZEOF.get_or_init(|| {
let sys = PyModule::import(py, "synapse.util.caches.lrucache").unwrap();
let func = sys.getattr("_get_size_of").unwrap().unbind();
func
});
let size: u64 = getsizeof.call1(py, (obj,)).unwrap().extract(py).unwrap();
size
}
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.
One thing I did when I did the pyo3-twisted bridge POC was to store and restore the contextvars during the future execution. If we switched to storing the logging context in contextvars, it would solve the logcontext being lost, but maybe not the resource usage metering. You can still probably take inspiration from that?
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.
I think calling
make_deferred_yieldable
from the Rust side (and therefore not having the wrapper) would be a reasonable alternative
This would be my choice if we can get it working.
(haven't tried yet since I'm trying to write a test to exercise this part of the code easily, #18903 (comment))
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 to merge or otherwise would be happy to see the wrapperless approach if you have the energy for it
from synapse.types import ISynapseReactor | ||
|
||
|
||
class HttpClient: |
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.
I think calling make_deferred_yieldable
from the Rust side (and therefore not having the wrapper) would be a reasonable alternative, but I also don't think this wrapper approach is too bad — at least it's extremely clear. (Maybe a bit easy to forget about using it?)
If you want a sketch of calling Python code from Rust, here's how I called _get_size_of
for my caching fun, whilst only importing it once:
static GETSIZEOF: OnceLock<pyo3::Py<pyo3::PyAny>> = OnceLock::new();
fn get_size_of(py: Python<'_>, obj: &Bound<'_, PyAny>) -> u64 {
let getsizeof = GETSIZEOF.get_or_init(|| {
let sys = PyModule::import(py, "synapse.util.caches.lrucache").unwrap();
let func = sys.getattr("_get_size_of").unwrap().unbind();
func
});
let size: u64 = getsizeof.call1(py, (obj,)).unwrap().extract(py).unwrap();
size
}
…ve-preserve-logging-context
# This triggers the server startup hooks, which starts the Tokio thread pool | ||
reactor.run() |
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.
I'm trying to add a test for this but running into Tokio runtime is not running
$ SYNAPSE_TEST_LOG_LEVEL=INFO poetry run trial tests.synapse_rust.test_http_client
The "poetry.dev-dependencies" section is deprecated and will be removed in a future version. Use "poetry.group.dev.dependencies" instead.
tests.synapse_rust.test_http_client
HttpClientTestCase
test_logging_context ... [FAIL]
===============================================================================
[FAIL]
Traceback (most recent call last):
File "synapse/tests/synapse_rust/test_http_client.py", line 87, in test_logging_context
self.get_success(self.till_deferred_has_result(asdf()))
File "synapse/tests/unittest.py", line 693, in get_success
return self.successResultOf(deferred)
File "virtualenvs/matrix-synapse-xCtC9ulO-py3.13/lib/python3.13/site-packages/twisted/trial/_synctest.py", line 732, in successResultOf
self.fail(
twisted.trial.unittest.FailTest: Success result expected on <Deferred at 0x7f928e6f5010 current result: None>, found failure result instead:
Traceback (most recent call last):
File "virtualenvs/matrix-synapse-xCtC9ulO-py3.13/lib/python3.13/site-packages/twisted/internet/defer.py", line 1857, in _inlineCallbacks
result = context.run(gen.send, result)
File "synapse/tests/synapse_rust/test_http_client.py", line 82, in asdf
await self._rust_http_client.get(
builtins.RuntimeError: Tokio runtime is not running
tests.synapse_rust.test_http_client.HttpClientTestCase.test_logging_context
-------------------------------------------------------------------------------
Ran 1 tests in 0.146s
FAILED (failures=1)
I can see that the tokio
runtime is supposed to start when the Twisted reactor starts:
synapse/rust/src/http_client.rs
Lines 115 to 117 in c68c5dd
// Attach the runtime to the reactor, starting it when the reactor is | |
// running, stopping it when the reactor is shutting down | |
reactor.call_method1("callWhenRunning", (runtime.getattr("start")?,))?; |
(introduced in #18691)
If I add in my own hook in the test itself hs.get_reactor().callWhenRunning(print, "asdf")
, it prints.
My test is based off of the existing MasAuthDelegation
tests but those actually seem to work. Perhaps because the Rust action is happening within the Synapse code instead of in the test code where there might be enough indirection delay for tokio to startup fully.
I can't figure out how to wait that little bit longer for tokio to startup.
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.
@sandhose Any insight? 🙇
…ve-preserve-logging-context
Wrap the Rust HTTP client with
make_deferred_yieldable
so downstream usage doesn't need to usePreserveLoggingContext()
ormake_deferred_yieldable
.Spawning from wanting to remove
PreserveLoggingContext()
from the codebase and thinking that we shouldn't have to pollute all downstream usage withPreserveLoggingContext()
ormake_deferred_yieldable
Part of #18905 (Remove
sentinel
logcontext where we log in Synapse)Dev notes
Pull Request Checklist
EventStore
toEventWorkerStore
.".code blocks
.