Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Docs/net-http-over-tls.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,10 @@ As built:
register their waiter with it. nio4r masks off the kernel's answer for what a hook owns.
- `Transport__` is the hook of the TCP socket it runs on. Every read, write and handshake is
one of SslStream's asynchronous operations, at most one of each kind in flight:
`#read_nonblock`, `#write_nonblock` (at most 16 KB handed over at a time, so what it reports
written is about what a kernel buffer holds) and `#connect_nonblock` start one and answer
`#read_nonblock`, `#write_nonblock` (16 KB records handed over for as long as each goes out
at once, up to 1 MB, so what it reports written is about what a kernel buffer holds; net/protocol
copies the rest of the body after every partial write, so 16 KB a call made a 64 MB body cost
gigabytes of copying) and `#connect_nonblock` start one and answer
`:wait_readable`/`:wait_writable` while it runs; `#read`, `#write` and `#connect` start one
or take over the one in flight, and wait for it.
- Asking the hook whether a read would return starts that read. SslStream may hold bytes,
Expand Down
52 changes: 32 additions & 20 deletions Src/Libraries/OpenSSL/OpenSSL.cs
Original file line number Diff line number Diff line change
Expand Up @@ -592,10 +592,16 @@ public class Transport : IReadinessHook {
private bool _closed;
private readonly List<ReadinessWaiter>/*!*/ _waiters = new List<ReadinessWaiter>();

// A TLS record carries at most 16 KB; a non-blocking write hands over no more than
// that at a time, so that what it reports as written is about what a kernel buffer holds.
// A TLS record carries at most 16 KB; a non-blocking write hands over one at a time,
// and goes on with the next only while the last one went out at once, so that what it
// reports as written is about what a kernel buffer holds.
private const int ChunkSize = 16 * 1024;

// How much a non-blocking write hands over at most. net/protocol slices off what was
// written and passes the rest back, a copy of the rest every time: 16 KB a call made
// a 64 MB body cost gigabytes of copying before the kernel buffers were even full.
private const int NonBlockingWriteLimit = 1024 * 1024;

// The context's own CAs (ca_file, ca_path, cert_store): the self-signed ones
// are the roots, the others may complete the chain. Null means the system
// store's; _systemTrust says whether the system's count as well.
Expand Down Expand Up @@ -1140,8 +1146,9 @@ public static int Write(Transport/*!*/ self, [DefaultProtocol, NotNull]MutableSt
}

/// <summary>
/// How much of data was handed over - at most 16 KB, which may still be on its way,
/// as bytes in a kernel buffer are - or :wait_writable while the last one is.
/// How much of data was handed over - 16 KB records for as long as they go out at once,
/// up to 1 MB; the last may still be on its way, as bytes in a kernel buffer are - or
/// :wait_writable while the last one is.
/// </summary>
[RubyMethod("write_nonblock")]
public static object WriteNonBlocking(Transport/*!*/ self, [DefaultProtocol, NotNull]MutableString/*!*/ data) {
Expand All @@ -1156,24 +1163,29 @@ public static object WriteNonBlocking(Transport/*!*/ self, [DefaultProtocol, Not
if (pending != null) {
self.FinishWrite(pending);
}
byte[] bytes = data.ConvertToBytes();
int count = Math.Min(bytes.Length, ChunkSize);
Task task;
lock (self._sync) {
var stream = self.Stream;
try {
task = stream.WriteAsync(bytes, 0, count);
} catch (Exception error) {
throw self.Translate(error);
}
if (!task.IsCompleted) {
self._pendingWrite = task;
self.NotifyOnCompletion(task);
return count;
// only what may go out: converting all of a large body on every call is a copy of it
byte[] bytes = data.GetBinarySlice(0, Math.Min(data.GetByteCount(), NonBlockingWriteLimit));
int written = 0;
while (written < bytes.Length) {
int count = Math.Min(bytes.Length - written, ChunkSize);
Task task;
lock (self._sync) {
var stream = self.Stream;
try {
task = stream.WriteAsync(bytes, written, count);
} catch (Exception error) {
throw self.Translate(error);
}
if (!task.IsCompleted) {
self._pendingWrite = task;
self.NotifyOnCompletion(task);
return written + count;
}
}
self.FinishWrite(task);
written += count;
}
self.FinishWrite(task);
return count;
return written;
}

[RubyMethod("close")]
Expand Down
3 changes: 2 additions & 1 deletion Src/StdLib/ironruby/openssl/ssl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,8 @@ def write(*data)
data.inject(0) { |total, item| total + syswrite(item.to_s) }
end

# How much of +data+ went out - at most 16 KB at a time - or SSLErrorWaitWritable
# How much of +data+ went out - 16 KB records while they go out at once, at most
# 1 MB - or SSLErrorWaitWritable
# (:wait_writable with exception: false) while the last part is still going.
def write_nonblock(data, exception: true)
connect unless @connected
Expand Down
Loading