Skip to content

Commit 32a1440

Browse files
authored
Merge pull request #13 from RustedBytes/profiler
fix one hot path
2 parents 60fc725 + 4166f95 commit 32a1440

File tree

6 files changed

+218
-30
lines changed

6 files changed

+218
-30
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rsloop"
3-
version = "0.1.7"
3+
version = "0.1.8"
44
edition = "2021"
55
description = "An event loop for asyncio written in Rust"
66
license = "Apache-2.0"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "maturin"
44

55
[project]
66
name = "rsloop"
7-
version = "0.1.7"
7+
version = "0.1.8"
88
description = "An event loop for asyncio written in Rust"
99
readme = "README.md"
1010
license = { file = "LICENSE" }

src/callbacks.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub type CallbackId = u64;
1414

1515
enum CallbackArgs {
1616
None,
17+
One(Py<PyAny>),
1718
Many(Py<PyTuple>),
1819
}
1920

@@ -50,6 +51,12 @@ impl ReadyCallback {
5051
) -> Self {
5152
let args = match args.bind(py).len() {
5253
0 => CallbackArgs::None,
54+
1 => CallbackArgs::One(
55+
args.bind(py)
56+
.get_item(0)
57+
.expect("single callback arg")
58+
.unbind(),
59+
),
5360
_ => CallbackArgs::Many(args),
5461
};
5562

@@ -123,13 +130,27 @@ impl ReadyCallback {
123130
)
124131
.map(Bound::unbind)
125132
},
133+
CallbackArgs::One(arg) => unsafe {
134+
Bound::from_owned_ptr_or_err(
135+
py,
136+
ffi::PyObject_CallFunctionObjArgs(
137+
self.callback.as_ptr(),
138+
arg.as_ptr(),
139+
std::ptr::null_mut::<ffi::PyObject>(),
140+
),
141+
)
142+
.map(Bound::unbind)
143+
},
126144
CallbackArgs::Many(args) => self.callback.call1(py, args.clone_ref(py)),
127145
}
128146
}
129147

130148
pub fn clone_args_tuple(&self, py: Python<'_>) -> Py<PyTuple> {
131149
match &self.args {
132150
CallbackArgs::None => PyTuple::empty(py).unbind(),
151+
CallbackArgs::One(arg) => PyTuple::new(py, [arg.bind(py)])
152+
.expect("single callback arg tuple")
153+
.unbind(),
133154
CallbackArgs::Many(args) => args.clone_ref(py),
134155
}
135156
}

src/context.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,107 @@ pub fn run_in_context(
8888
}
8989
}
9090

91+
#[inline]
92+
pub fn run_in_context_noargs(
93+
py: Python<'_>,
94+
context: &Py<PyAny>,
95+
needs_run: bool,
96+
callback: &Py<PyAny>,
97+
) -> PyResult<Py<PyAny>> {
98+
if !needs_run {
99+
return unsafe {
100+
Bound::from_owned_ptr_or_err(py, ffi::compat::PyObject_CallNoArgs(callback.as_ptr()))
101+
}
102+
.map(Bound::unbind);
103+
}
104+
105+
if let Err(err) = enter_context(py, context) {
106+
return if is_nested_context_error(py, &err) {
107+
unsafe {
108+
Bound::from_owned_ptr_or_err(
109+
py,
110+
ffi::compat::PyObject_CallNoArgs(callback.as_ptr()),
111+
)
112+
}
113+
.map(Bound::unbind)
114+
} else {
115+
Err(err)
116+
};
117+
}
118+
119+
let callback_result = unsafe {
120+
Bound::from_owned_ptr_or_err(py, ffi::compat::PyObject_CallNoArgs(callback.as_ptr()))
121+
}
122+
.map(Bound::unbind);
123+
let exit_result = exit_context(py, context);
124+
125+
match (callback_result, exit_result) {
126+
(Ok(result), Ok(())) => Ok(result),
127+
(Err(err), _) => Err(err),
128+
(Ok(_), Err(err)) => Err(err),
129+
}
130+
}
131+
132+
#[inline]
133+
pub fn run_in_context_onearg(
134+
py: Python<'_>,
135+
context: &Py<PyAny>,
136+
needs_run: bool,
137+
callback: &Py<PyAny>,
138+
arg: &Bound<'_, PyAny>,
139+
) -> PyResult<Py<PyAny>> {
140+
if !needs_run {
141+
return unsafe {
142+
Bound::from_owned_ptr_or_err(
143+
py,
144+
ffi::PyObject_CallFunctionObjArgs(
145+
callback.as_ptr(),
146+
arg.as_ptr(),
147+
std::ptr::null_mut::<ffi::PyObject>(),
148+
),
149+
)
150+
}
151+
.map(Bound::unbind);
152+
}
153+
154+
if let Err(err) = enter_context(py, context) {
155+
return if is_nested_context_error(py, &err) {
156+
unsafe {
157+
Bound::from_owned_ptr_or_err(
158+
py,
159+
ffi::PyObject_CallFunctionObjArgs(
160+
callback.as_ptr(),
161+
arg.as_ptr(),
162+
std::ptr::null_mut::<ffi::PyObject>(),
163+
),
164+
)
165+
}
166+
.map(Bound::unbind)
167+
} else {
168+
Err(err)
169+
};
170+
}
171+
172+
let callback_result = unsafe {
173+
Bound::from_owned_ptr_or_err(
174+
py,
175+
ffi::PyObject_CallFunctionObjArgs(
176+
callback.as_ptr(),
177+
arg.as_ptr(),
178+
std::ptr::null_mut::<ffi::PyObject>(),
179+
),
180+
)
181+
}
182+
.map(Bound::unbind);
183+
let exit_result = exit_context(py, context);
184+
185+
match (callback_result, exit_result) {
186+
(Ok(result), Ok(())) => Ok(result),
187+
(Err(err), _) => Err(err),
188+
(Ok(_), Err(err)) => Err(err),
189+
}
190+
}
191+
91192
#[inline]
92193
pub fn ensure_running_loop(py: Python<'_>, loop_obj: &Py<PyAny>) -> PyResult<()> {
93194
set_running_loop_fn(py)?.call1(py, (loop_obj.clone_ref(py),))?;

src/stream_transport.rs

Lines changed: 93 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ use tokio::io::AsyncReadExt;
3434
use vibeio::net::{PollTcpStream as VibePollTcpStream, TcpListener as VibeTcpListener};
3535

3636
use crate::async_event::AsyncEvent;
37-
use crate::context::{ensure_running_loop, run_in_context};
37+
use crate::context::{
38+
ensure_running_loop, run_in_context, run_in_context_noargs, run_in_context_onearg,
39+
};
3840
use crate::fast_streams::{PyFastStreamProtocol, PyFastStreamReader};
3941
use crate::fd_ops;
4042
use crate::loop_core::{LoopCommand, LoopCore};
@@ -59,6 +61,7 @@ enum PendingReadEvent {
5961

6062
const DEFAULT_WRITE_BUFFER_HIGH_WATER: usize = 64 * 1024;
6163
const DEFAULT_WRITE_BUFFER_LOW_WATER: usize = DEFAULT_WRITE_BUFFER_HIGH_WATER / 4;
64+
const MAX_PENDING_READ_COALESCE_BYTES: usize = 256 * 1024;
6265

6366
struct OwnedWriteBuffer {
6467
bytes: Box<[u8]>,
@@ -772,18 +775,6 @@ impl StreamTransportCore {
772775
spawn_writer_worker(Arc::clone(self), target, writer_rx);
773776
}
774777

775-
fn call_protocol_with_tuple(
776-
&self,
777-
py: Python<'_>,
778-
callback: &Py<PyAny>,
779-
context: &Py<PyAny>,
780-
context_needs_run: bool,
781-
args: &Bound<'_, PyTuple>,
782-
) -> PyResult<Py<PyAny>> {
783-
let tuple = args.clone().unbind();
784-
run_in_context(py, context, context_needs_run, callback, &tuple)
785-
}
786-
787778
fn server_ref(&self) -> Option<Weak<ServerCore>> {
788779
self.state
789780
.lock()
@@ -840,14 +831,39 @@ impl StreamTransportCore {
840831
drained
841832
};
842833

834+
let mut pending_data: Option<Vec<u8>> = None;
835+
843836
while let Some(event) = pending.pop_front() {
844837
match event {
845838
PendingReadEvent::Data(data) => {
846839
profiling::scope!("stream.pending.data");
847-
if self.is_closing_or_lost() {
848-
continue;
840+
match pending_data.as_mut() {
841+
Some(buffer)
842+
if buffer.len() + data.len() <= MAX_PENDING_READ_COALESCE_BYTES =>
843+
{
844+
buffer.extend_from_slice(&data);
845+
}
846+
Some(_) => {
847+
if let Err(err) =
848+
self.flush_pending_data_with_py(py, &mut pending_data)
849+
{
850+
let _ = self.report_error_with_py(
851+
py,
852+
err,
853+
"stream data_received callback failed",
854+
);
855+
let _ = self.connection_lost_with_py(py, None);
856+
self.read_events_scheduled.store(false, Ordering::Release);
857+
return Ok(());
858+
}
859+
pending_data = Some(data.into_vec());
860+
}
861+
None => pending_data = Some(data.into_vec()),
849862
}
850-
if let Err(err) = self.data_received_with_py(py, &data) {
863+
}
864+
PendingReadEvent::Eof => {
865+
profiling::scope!("stream.pending.eof");
866+
if let Err(err) = self.flush_pending_data_with_py(py, &mut pending_data) {
851867
let _ = self.report_error_with_py(
852868
py,
853869
err,
@@ -857,9 +873,6 @@ impl StreamTransportCore {
857873
self.read_events_scheduled.store(false, Ordering::Release);
858874
return Ok(());
859875
}
860-
}
861-
PendingReadEvent::Eof => {
862-
profiling::scope!("stream.pending.eof");
863876
match self.eof_received_with_py(py) {
864877
Ok(true) => {
865878
self.read_events_scheduled.store(false, Ordering::Release);
@@ -885,21 +898,58 @@ impl StreamTransportCore {
885898
}
886899
PendingReadEvent::ConnectionLost(message) => {
887900
profiling::scope!("stream.pending.connection_lost");
901+
if let Err(err) = self.flush_pending_data_with_py(py, &mut pending_data) {
902+
let _ = self.report_error_with_py(
903+
py,
904+
err,
905+
"stream data_received callback failed",
906+
);
907+
let _ = self.connection_lost_with_py(py, None);
908+
self.read_events_scheduled.store(false, Ordering::Release);
909+
return Ok(());
910+
}
888911
let err = message.map(PyRuntimeError::new_err);
889912
let _ = self.connection_lost_with_py(py, err);
890913
self.read_events_scheduled.store(false, Ordering::Release);
891914
return Ok(());
892915
}
893916
PendingReadEvent::PauseWriting => {
894917
profiling::scope!("stream.pending.pause_writing");
918+
if let Err(err) = self.flush_pending_data_with_py(py, &mut pending_data) {
919+
let _ = self.report_error_with_py(
920+
py,
921+
err,
922+
"stream data_received callback failed",
923+
);
924+
let _ = self.connection_lost_with_py(py, None);
925+
self.read_events_scheduled.store(false, Ordering::Release);
926+
return Ok(());
927+
}
895928
self.pause_writing_with_py(py)?;
896929
}
897930
PendingReadEvent::ResumeWriting => {
898931
profiling::scope!("stream.pending.resume_writing");
932+
if let Err(err) = self.flush_pending_data_with_py(py, &mut pending_data) {
933+
let _ = self.report_error_with_py(
934+
py,
935+
err,
936+
"stream data_received callback failed",
937+
);
938+
let _ = self.connection_lost_with_py(py, None);
939+
self.read_events_scheduled.store(false, Ordering::Release);
940+
return Ok(());
941+
}
899942
self.resume_writing_with_py(py)?;
900943
}
901944
}
902945
}
946+
947+
if let Err(err) = self.flush_pending_data_with_py(py, &mut pending_data) {
948+
let _ = self.report_error_with_py(py, err, "stream data_received callback failed");
949+
let _ = self.connection_lost_with_py(py, None);
950+
self.read_events_scheduled.store(false, Ordering::Release);
951+
return Ok(());
952+
}
903953
}
904954
}
905955

@@ -910,8 +960,7 @@ impl StreamTransportCore {
910960
context: &Py<PyAny>,
911961
context_needs_run: bool,
912962
) -> PyResult<Py<PyAny>> {
913-
let args = PyTuple::empty(py);
914-
self.call_protocol_with_tuple(py, callback, context, context_needs_run, &args)
963+
run_in_context_noargs(py, context, context_needs_run, callback)
915964
}
916965

917966
fn call_protocol_method1(
@@ -922,8 +971,23 @@ impl StreamTransportCore {
922971
context_needs_run: bool,
923972
arg: Py<PyAny>,
924973
) -> PyResult<Py<PyAny>> {
925-
let args = PyTuple::new(py, [arg])?;
926-
self.call_protocol_with_tuple(py, callback, context, context_needs_run, &args)
974+
run_in_context_onearg(py, context, context_needs_run, callback, arg.bind(py))
975+
}
976+
977+
fn flush_pending_data_with_py(
978+
&self,
979+
py: Python<'_>,
980+
pending_data: &mut Option<Vec<u8>>,
981+
) -> PyResult<()> {
982+
let Some(data) = pending_data.take() else {
983+
return Ok(());
984+
};
985+
986+
if self.is_closing_or_lost() {
987+
return Ok(());
988+
}
989+
990+
self.data_received_with_py(py, &data)
927991
}
928992

929993
fn report_error_with_py(&self, py: Python<'_>, err: PyErr, message: &str) -> PyResult<()> {
@@ -1024,10 +1088,12 @@ impl StreamTransportCore {
10241088
{
10251089
let args = PyTuple::new(py, [data.len()])?.unbind();
10261090
let buffer_obj = run_in_context(py, &context, context_needs_run, get_buffer, &args)?;
1027-
let memoryview = py
1028-
.import("builtins")?
1029-
.getattr("memoryview")?
1030-
.call1((buffer_obj.bind(py),))?;
1091+
let memoryview = unsafe {
1092+
Bound::from_owned_ptr_or_err(
1093+
py,
1094+
pyo3::ffi::PyMemoryView_FromObject(buffer_obj.bind(py).as_ptr()),
1095+
)
1096+
}?;
10311097
memoryview.set_item(
10321098
PySlice::new(py, 0, data.len() as isize, 1),
10331099
PyBytes::new(py, data),

0 commit comments

Comments
 (0)