Skip to content

Commit 0d8c17a

Browse files
committed
Reduce OpenSSL::Buffering#do_write overhead
[Bug #20972] The `rb_str_new_freeze` was added in #452 to better handle concurrent use of a Socket, but SSL sockets can't be used concurrently AFAIK, so we might as well just error cleanly. By using `rb_str_locktmp` we can ensure attempts at concurrent write will raise an error, be we avoid causing a copy of the bytes. We also use the newer `String#append_as_bytes` method when available to save on some more copies. Co-Authored-By: luke.gru@gmail.com
1 parent b9ef9cc commit 0d8c17a

File tree

2 files changed

+58
-25
lines changed

2 files changed

+58
-25
lines changed

ext/openssl/ossl_ssl.c

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2054,28 +2054,32 @@ ossl_ssl_read_nonblock(int argc, VALUE *argv, VALUE self)
20542054
}
20552055

20562056
static VALUE
2057-
ossl_ssl_write_internal(VALUE self, VALUE str, VALUE opts)
2057+
ossl_ssl_write_internal_safe(VALUE _args)
20582058
{
2059+
VALUE *args = (VALUE*)_args;
2060+
VALUE self = args[0];
2061+
VALUE str = args[1];
2062+
VALUE opts = args[2];
2063+
20592064
SSL *ssl;
20602065
rb_io_t *fptr;
20612066
int num, nonblock = opts != Qfalse;
2062-
VALUE tmp, cb_state;
2067+
VALUE cb_state;
20632068

20642069
GetSSL(self, ssl);
20652070
if (!ssl_started(ssl))
20662071
rb_raise(eSSLError, "SSL session is not started yet");
20672072

2068-
tmp = rb_str_new_frozen(StringValue(str));
20692073
VALUE io = rb_attr_get(self, id_i_io);
20702074
GetOpenFile(io, fptr);
20712075

20722076
/* SSL_write(3ssl) manpage states num == 0 is undefined */
2073-
num = RSTRING_LENINT(tmp);
2077+
num = RSTRING_LENINT(str);
20742078
if (num == 0)
20752079
return INT2FIX(0);
20762080

20772081
for (;;) {
2078-
int nwritten = SSL_write(ssl, RSTRING_PTR(tmp), num);
2082+
int nwritten = SSL_write(ssl, RSTRING_PTR(str), num);
20792083

20802084
cb_state = rb_attr_get(self, ID_callback_state);
20812085
if (!NIL_P(cb_state)) {
@@ -2116,6 +2120,29 @@ ossl_ssl_write_internal(VALUE self, VALUE str, VALUE opts)
21162120
}
21172121
}
21182122

2123+
2124+
static VALUE
2125+
ossl_ssl_write_internal(VALUE self, VALUE str, VALUE opts)
2126+
{
2127+
VALUE args[3] = {self, str, opts};
2128+
int state;
2129+
str = StringValue(str);
2130+
2131+
int frozen = RB_OBJ_FROZEN(str);
2132+
if (!frozen) {
2133+
str = rb_str_locktmp(str);
2134+
}
2135+
VALUE result = rb_protect(ossl_ssl_write_internal_safe, (VALUE)args, &state);
2136+
if (!frozen) {
2137+
rb_str_unlocktmp(str);
2138+
}
2139+
2140+
if (state) {
2141+
rb_jump_tag(state);
2142+
}
2143+
return result;
2144+
}
2145+
21192146
/*
21202147
* call-seq:
21212148
* ssl.syswrite(string) => Integer

lib/openssl/buffering.rb

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,21 @@ module OpenSSL::Buffering
2424

2525
# A buffer which will retain binary encoding.
2626
class Buffer < String
27-
BINARY = Encoding::BINARY
28-
29-
def initialize
30-
super
31-
32-
force_encoding(BINARY)
33-
end
27+
unless String.method_defined?(:append_as_bytes)
28+
alias_method :_append, :<<
29+
def append_as_bytes(string)
30+
if string.encoding == Encoding::BINARY
31+
_append(string)
32+
else
33+
_append(string.b)
34+
end
3435

35-
def << string
36-
if string.encoding == BINARY
37-
super(string)
38-
else
39-
super(string.b)
36+
self
4037
end
41-
42-
return self
4338
end
4439

45-
alias concat <<
40+
alias_method :concat, :append_as_bytes
41+
alias_method :<<, :append_as_bytes
4642
end
4743

4844
##
@@ -352,22 +348,32 @@ def eof?
352348

353349
def do_write(s)
354350
@wbuffer = Buffer.new unless defined? @wbuffer
355-
@wbuffer << s
356-
@wbuffer.force_encoding(Encoding::BINARY)
351+
@wbuffer.append_as_bytes(s)
352+
357353
@sync ||= false
358-
buffer_size = @wbuffer.size
354+
buffer_size = @wbuffer.bytesize
359355
if @sync or buffer_size > BLOCK_SIZE
360356
nwrote = 0
361357
begin
362358
while nwrote < buffer_size do
363359
begin
364-
nwrote += syswrite(@wbuffer[nwrote, buffer_size - nwrote])
360+
chunk = if nwrote > 0
361+
@wbuffer.byteslice(nwrote, @wbuffer.bytesize)
362+
else
363+
@wbuffer
364+
end
365+
366+
nwrote += syswrite(chunk)
365367
rescue Errno::EAGAIN
366368
retry
367369
end
368370
end
369371
ensure
370-
@wbuffer[0, nwrote] = ""
372+
if nwrote < @wbuffer.bytesize
373+
@wbuffer[0, nwrote] = ""
374+
else
375+
@wbuffer.clear
376+
end
371377
end
372378
end
373379
end

0 commit comments

Comments
 (0)