diff options
Diffstat (limited to 'js')
-rw-r--r-- | js/net.ts | 26 | ||||
-rw-r--r-- | js/net_test.ts | 113 | ||||
-rw-r--r-- | js/util.ts | 26 |
3 files changed, 161 insertions, 4 deletions
@@ -55,6 +55,8 @@ class ListenerImpl implements Listener { export interface Conn extends Reader, Writer, Closer { localAddr: string; remoteAddr: string; + closeRead(): void; + closeWrite(): void; } class ConnImpl implements Conn { @@ -80,19 +82,35 @@ class ConnImpl implements Conn { * Most callers should just use close(). */ closeRead(): void { - // TODO(ry) Connect to AsyncWrite::shutdown in resources.rs - return notImplemented(); + shutdown(this.fd, ShutdownMode.Read); } /** closeWrite shuts down (shutdown(2)) the writing side of the TCP * connection. Most callers should just use close(). */ closeWrite(): void { - // TODO(ry) Connect to AsyncWrite::shutdown in resources.rs - return notImplemented(); + shutdown(this.fd, ShutdownMode.Write); } } +enum ShutdownMode { + // See http://man7.org/linux/man-pages/man2/shutdown.2.html + // Corresponding to SHUT_RD, SHUT_WR, SHUT_RDWR + Read = 0, + Write, + ReadWrite // unused +} + +function shutdown(fd: number, how: ShutdownMode) { + const builder = new flatbuffers.Builder(); + msg.Shutdown.startShutdown(builder); + msg.Shutdown.addRid(builder, fd); + msg.Shutdown.addHow(builder, how); + const inner = msg.Shutdown.endShutdown(builder); + const baseRes = dispatch.sendSync(builder, msg.Any.Shutdown, inner); + assert(baseRes == null); +} + /** Listen announces on the local network address. * * The network must be "tcp", "tcp4", "tcp6", "unix" or "unixpacket". diff --git a/js/net_test.ts b/js/net_test.ts index 93ad6ee00..c7c8dbb59 100644 --- a/js/net_test.ts +++ b/js/net_test.ts @@ -2,6 +2,7 @@ import * as deno from "deno"; import { testPerm, assert, assertEqual } from "./test_util.ts"; +import { deferred } from "./util.ts"; testPerm({ net: true }, function netListenClose() { const listener = deno.listen("tcp", "127.0.0.1:4500"); @@ -35,3 +36,115 @@ testPerm({ net: true }, async function netDialListen() { listener.close(); conn.close(); }); + +testPerm({ net: true }, async function netCloseReadSuccess() { + const addr = "127.0.0.1:4500"; + const listener = deno.listen("tcp", addr); + const closeDeferred = deferred(); + listener.accept().then(async conn => { + await conn.write(new Uint8Array([1, 2, 3])); + const buf = new Uint8Array(1024); + const readResult = await conn.read(buf); + assertEqual(3, readResult.nread); + assertEqual(4, buf[0]); + assertEqual(5, buf[1]); + assertEqual(6, buf[2]); + conn.close(); + closeDeferred.resolve(); + }); + const conn = await deno.dial("tcp", addr); + conn.closeRead(); // closing read + const buf = new Uint8Array(1024); + const readResult = await conn.read(buf); + assertEqual(0, readResult.nread); // No error, read nothing + assertEqual(true, readResult.eof); // with immediate EOF + // Ensure closeRead does not impact write + await conn.write(new Uint8Array([4, 5, 6])); + await closeDeferred.promise; + listener.close(); + conn.close(); +}); + +testPerm({ net: true }, async function netDoubleCloseRead() { + const addr = "127.0.0.1:4500"; + const listener = deno.listen("tcp", addr); + const closeDeferred = deferred(); + listener.accept().then(async conn => { + await conn.write(new Uint8Array([1, 2, 3])); + await closeDeferred.promise; + conn.close(); + }); + const conn = await deno.dial("tcp", addr); + conn.closeRead(); // closing read + let err; + try { + // Duplicated close should throw error + conn.closeRead(); + } catch (e) { + err = e; + } + assert(!!err); + assertEqual(err.kind, deno.ErrorKind.NotConnected); + assertEqual(err.name, "NotConnected"); + closeDeferred.resolve(); + listener.close(); + conn.close(); +}); + +testPerm({ net: true }, async function netCloseWriteSuccess() { + const addr = "127.0.0.1:4500"; + const listener = deno.listen("tcp", addr); + const closeDeferred = deferred(); + listener.accept().then(async conn => { + await conn.write(new Uint8Array([1, 2, 3])); + await closeDeferred.promise; + conn.close(); + }); + const conn = await deno.dial("tcp", addr); + conn.closeWrite(); // closing write + const buf = new Uint8Array(1024); + // Check read not impacted + const readResult = await conn.read(buf); + assertEqual(3, readResult.nread); + assertEqual(1, buf[0]); + assertEqual(2, buf[1]); + assertEqual(3, buf[2]); + // Check write should be closed + let err; + try { + await conn.write(new Uint8Array([1, 2, 3])); + } catch (e) { + err = e; + } + assert(!!err); + assertEqual(err.kind, deno.ErrorKind.BrokenPipe); + assertEqual(err.name, "BrokenPipe"); + closeDeferred.resolve(); + listener.close(); + conn.close(); +}); + +testPerm({ net: true }, async function netDoubleCloseWrite() { + const addr = "127.0.0.1:4500"; + const listener = deno.listen("tcp", addr); + const closeDeferred = deferred(); + listener.accept().then(async conn => { + await closeDeferred.promise; + conn.close(); + }); + const conn = await deno.dial("tcp", addr); + conn.closeWrite(); // closing write + let err; + try { + // Duplicated close should throw error + conn.closeWrite(); + } catch (e) { + err = e; + } + assert(!!err); + assertEqual(err.kind, deno.ErrorKind.NotConnected); + assertEqual(err.name, "NotConnected"); + closeDeferred.resolve(); + listener.close(); + conn.close(); +}); diff --git a/js/util.ts b/js/util.ts index bfde01908..de6a078bb 100644 --- a/js/util.ts +++ b/js/util.ts @@ -101,3 +101,29 @@ export function containsOnlyASCII(str: string): boolean { } return /^[\x00-\x7F]*$/.test(str); } + +// @internal +export interface Deferred { + promise: Promise<void>; + resolve: Function; + reject: Function; +} + +/** + * Create a wrapper around a promise that could be + * resolved externally. + * @internal + */ +export function deferred(): Deferred { + let resolve: Function | undefined; + let reject: Function | undefined; + const promise = new Promise<void>((res, rej) => { + resolve = res; + reject = rej; + }); + return { + promise, + resolve: resolve!, + reject: reject! + }; +} |