Skip to content

Commit fe3cf7e

Browse files
Let us use a reshareable string buffer.
1 parent 5f3abd7 commit fe3cf7e

File tree

3 files changed

+40
-28
lines changed

3 files changed

+40
-28
lines changed

ext/hyper_ruby/src/lib.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use gvl_helpers::nogvl;
1010
use magnus::block::block_proc;
1111
use magnus::r_hash::ForEach;
1212
use magnus::typed_data::Obj;
13-
use magnus::{function, method, prelude::*, Error as MagnusError, IntoValue, Ruby, Value};
13+
use magnus::{function, method, prelude::*, Error as MagnusError, IntoValue, RString, Ruby, Value};
1414
use bytes::Bytes;
1515

1616
use std::cell::RefCell;
@@ -46,7 +46,7 @@ impl ServerConfig {
4646

4747
// Sent on the work channel with the request, and a oneshot channel to send the response back on.
4848
struct RequestWithCompletion {
49-
request: Request,
49+
request: HyperRequest<Bytes>,
5050
// sent a response back on this thread
5151
response_tx: oneshot::Sender<HyperResponse<Full<Bytes>>>,
5252
}
@@ -85,13 +85,17 @@ impl Server {
8585
pub fn run_worker(&self) -> Result<(), MagnusError> {
8686
let block = block_proc().unwrap();
8787
if let Some(work_rx) = self.work_rx.borrow().as_ref() {
88+
8889
loop {
8990
// Use nogvl to wait for requests outside the GVL
9091
let work_request = nogvl(|| work_rx.recv());
9192

9293
match work_request {
9394
Ok(work_request) => {
94-
let value = unsafe { work_request.request.into_value_unchecked() };
95+
let request = Request {
96+
request: work_request.request,
97+
};
98+
let value = request.into_value();
9599
let hyper_response = match block.call::<_, Value>([value]) {
96100
Ok(result) => {
97101
let ref_response = Obj::<Response>::try_convert(result).unwrap();
@@ -253,14 +257,10 @@ async fn handle_request(
253257
let (parts, body) = req.into_parts();
254258
let body_bytes = body.collect().await?.to_bytes();
255259

256-
let request = Request {
257-
request: HyperRequest::from_parts(parts, body_bytes),
258-
};
259-
260260
let (response_tx, response_rx) = oneshot::channel();
261261

262262
let with_completion = RequestWithCompletion {
263-
request,
263+
request: HyperRequest::from_parts(parts, body_bytes),
264264
response_tx,
265265
};
266266

@@ -311,7 +311,7 @@ fn init(ruby: &Ruby) -> Result<(), MagnusError> {
311311
request_class.define_method("http_method", method!(Request::method, 0))?;
312312
request_class.define_method("path", method!(Request::path, 0))?;
313313
request_class.define_method("header", method!(Request::header, 1))?;
314-
request_class.define_method("body", method!(Request::body, 0))?;
314+
request_class.define_method("fill_body", method!(Request::fill_body, 1))?;
315315
request_class.define_method("body_size", method!(Request::body_size, 0))?;
316316
request_class.define_method("inspect", method!(Request::inspect, 0))?;
317317

ext/hyper_ruby/src/request.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
use magnus::{value::{qnil, ReprValue}, RString, Value};
1+
use magnus::{gc, value::{qnil, Opaque, ReprValue}, DataTypeFunctions, IntoValue, RString, Ruby, TypedData, Value};
2+
23
use bytes::Bytes;
34
use hyper::Request as HyperRequest;
45

6+
use rb_sys::{rb_str_resize, rb_str_cat, VALUE};
7+
58
// Type passed to ruby giving access to the request properties.
6-
#[derive(Debug)]
7-
#[magnus::wrap(class = "HyperRuby::Request", free_immediately)]
9+
#[magnus::wrap(class = "HyperRuby::Request")]
810
pub struct Request {
911
pub request: HyperRequest<Bytes>
1012
}
@@ -34,15 +36,22 @@ impl Request {
3436
self.request.body().len()
3537
}
3638

37-
pub fn body(&self) -> Value {
39+
pub fn fill_body(&self, buffer: RString) -> usize {
3840
let body = self.request.body();
39-
if body.is_empty() {
40-
return qnil().as_value();
41+
let body_len = body.len();
42+
43+
// Access the ruby string VALUE directly, and resize to 0 (keeping the capacity),
44+
// then copy our buffer into it.
45+
unsafe {
46+
let rb_value = buffer.as_value();
47+
let inner: VALUE = std::ptr::read(&rb_value as *const _ as *const VALUE);
48+
rb_str_resize(inner, 0);
49+
if body_len > 0 {
50+
rb_str_cat(inner, body.as_ptr() as *const i8, body.len().try_into().unwrap());
51+
}
4152
}
4253

43-
let result = RString::buf_new(body.len());
44-
result.cat(body.as_ref());
45-
result.as_value()
54+
body_len
4655
}
4756

4857
pub fn inspect(&self) -> RString {

test/test_hyper_ruby.rb

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ def test_header_fetch_get
2626
end
2727

2828
def test_simple_post
29-
with_server(-> (request) { handler_to_json(request) }) do |client|
29+
buffer = String.new(capacity: 1024)
30+
with_server(-> (request) { handler_to_json(request, buffer) }) do |client|
3031
response = client.post("/", body: "Hello")
3132
assert_equal 200, response.status
3233
assert_equal "application/json", response.headers["content-type"]
@@ -35,7 +36,8 @@ def test_simple_post
3536
end
3637

3738
def test_large_post
38-
with_server(-> (request) { handler_to_json(request) }) do |client|
39+
buffer = String.new(capacity: 1024)
40+
with_server(-> (request) { handler_to_json(request, buffer) }) do |client|
3941
response = client.post("/", body: "a" * 10_000_000)
4042
assert_equal 200, response.status
4143
assert_equal "application/json", response.headers["content-type"]
@@ -59,11 +61,12 @@ def test_unix_socket_cleans_up_socket
5961
end
6062
end
6163

62-
# def test_blocking
63-
# with_server(-> (request) { handler_simple(request) }) do |client|
64-
# gets
65-
# end
66-
# end
64+
def test_blocking
65+
buffer = String.new(capacity: 1024)
66+
with_server(-> (request) { handler_to_json(request, buffer) }) do |client|
67+
gets
68+
end
69+
end
6770

6871
def with_server(request_handler, &block)
6972
server = HyperRuby::Server.new
@@ -118,11 +121,11 @@ def handler_simple(request)
118121
HyperRuby::Response.new(200, { 'Content-Type' => 'text/plain' }, request.http_method)
119122
end
120123

121-
def handler_to_json(request)
122-
HyperRuby::Response.new(200, { 'Content-Type' => 'application/json' }, { message: request.body }.to_json)
124+
def handler_to_json(request, buffer)
125+
request.fill_body(buffer)
126+
HyperRuby::Response.new(200, { 'Content-Type' => 'application/json' }, { message: buffer }.to_json)
123127
end
124128

125-
126129
def handler_return_header(request, header_key)
127130
HyperRuby::Response.new(200, { 'Content-Type' => 'application/json' }, { message: request.header(header_key) }.to_json)
128131
end

0 commit comments

Comments
 (0)