From f18c421cab3d12e1f15c758850595d978b242960 Mon Sep 17 00:00:00 2001 From: Paulo Faria Date: Sat, 20 May 2017 17:38:58 -0300 Subject: [PATCH 1/2] add read and write to file descriptor, remove Handle --- .jazzy.yml | 4 - README.md | 10 +- Sources/Venice/Channel.swift | 156 ++++--- Sources/Venice/Coroutine.swift | 112 ++--- Sources/Venice/Error.swift | 38 +- Sources/Venice/FileDescriptor.swift | 227 ++++++++--- Sources/Venice/Handle.swift | 99 ----- Sources/Venice/List.swift | 58 --- Tests/VeniceTests/Venice/ChannelTests.swift | 137 +++---- Tests/VeniceTests/Venice/CoroutineTests.swift | 131 +++--- Tests/VeniceTests/Venice/TimeTests.swift | 8 +- docs/Channels.html | 30 +- docs/Classes/Channel.html | 116 +++--- docs/Classes/Channel/ReceiveOnly.html | 163 -------- docs/Classes/Channel/SendOnly.html | 190 --------- docs/Classes/Coroutine.html | 111 +++-- docs/Classes/Coroutine/Group.html | 61 +-- docs/Classes/FileDescriptor.html | 381 +++++++++++++++--- docs/Classes/FileDescriptor/PollEvent.html | 14 +- docs/Classes/Handle.html | 333 --------------- docs/Coroutines.html | 20 +- docs/Enums/VeniceError.html | 147 ++----- docs/Errors.html | 14 +- docs/Extensions/Int.html | 14 +- docs/File Descriptors.html | 16 +- docs/Handles.html | 147 ------- docs/Structs/Deadline.html | 14 +- docs/Structs/Duration.html | 14 +- docs/Time.html | 14 +- .../Resources/Documents/Channels.html | 30 +- .../Resources/Documents/Classes/Channel.html | 116 +++--- .../Classes/Channel/ReceiveOnly.html | 163 -------- .../Documents/Classes/Channel/SendOnly.html | 190 --------- .../Documents/Classes/Coroutine.html | 111 +++-- .../Documents/Classes/Coroutine/Group.html | 61 +-- .../Documents/Classes/FileDescriptor.html | 381 +++++++++++++++--- .../Classes/FileDescriptor/PollEvent.html | 14 +- .../Resources/Documents/Classes/Handle.html | 333 --------------- .../Resources/Documents/Coroutines.html | 20 +- .../Documents/Enums/VeniceError.html | 147 ++----- .../Contents/Resources/Documents/Errors.html | 14 +- .../Resources/Documents/Extensions/Int.html | 14 +- .../Resources/Documents/File Descriptors.html | 16 +- .../Contents/Resources/Documents/Handles.html | 147 ------- .../Resources/Documents/Structs/Deadline.html | 14 +- .../Resources/Documents/Structs/Duration.html | 14 +- .../Contents/Resources/Documents/Time.html | 14 +- .../Contents/Resources/Documents/index.html | 26 +- .../Contents/Resources/Documents/search.json | 2 +- .../Contents/Resources/docSet.dsidx | Bin 28672 -> 28672 bytes docs/docsets/Venice.tgz | Bin 63275 -> 61718 bytes docs/index.html | 26 +- docs/search.json | 2 +- 53 files changed, 1565 insertions(+), 3069 deletions(-) delete mode 100644 Sources/Venice/Handle.swift delete mode 100644 Sources/Venice/List.swift delete mode 100644 docs/Classes/Channel/ReceiveOnly.html delete mode 100644 docs/Classes/Channel/SendOnly.html delete mode 100644 docs/Classes/Handle.html delete mode 100644 docs/Handles.html delete mode 100644 docs/docsets/Venice.docset/Contents/Resources/Documents/Classes/Channel/ReceiveOnly.html delete mode 100644 docs/docsets/Venice.docset/Contents/Resources/Documents/Classes/Channel/SendOnly.html delete mode 100644 docs/docsets/Venice.docset/Contents/Resources/Documents/Classes/Handle.html delete mode 100644 docs/docsets/Venice.docset/Contents/Resources/Documents/Handles.html diff --git a/.jazzy.yml b/.jazzy.yml index 9a44fd2..407ac30 100644 --- a/.jazzy.yml +++ b/.jazzy.yml @@ -9,10 +9,6 @@ xcodebuild_arguments: - Venice custom_categories: - - name: Handles - children: - - Handle - - name: Coroutines children: - Coroutine diff --git a/README.md b/README.md index 7880e98..030b28b 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ import PackageDescription let package = Package( dependencies: [ - .Package(url: "https://github.com/Zewo/Venice.git", majorVersion: 0, minor: 17) + .Package(url: "https://github.com/Zewo/Venice.git", majorVersion: 0, minor: 18) ] ) ``` @@ -54,7 +54,7 @@ What you end up with is a tree of coroutines rooted in the `main` function. This ![call-tree](http://libdill.org/index3.jpeg "Call Tree") -Venice implements structured concurrency by allowing you to close a running coroutine. +Venice implements structured concurrency by allowing you to cancel a running coroutine. ```swift let coroutine = try Coroutine { @@ -71,12 +71,12 @@ let coroutine = try Coroutine { } try Coroutine.wakeUp(1.second.fromNow()) -try coroutine.close() +coroutine.cancel() ``` - When a coroutine is being closed all blocking calls will start to throw `VeniceError.canceled`. On one hand, this forces the function to finish quickly (there's not much you can do without blocking functions); on the other hand, it provides an opportunity for cleanup. + When a coroutine is being canceled all coroutine-blocking calls will start to throw `VeniceError.canceledCoroutine`. On one hand, this forces the function to finish quickly (there's not much you can do without coroutine-blocking functions); on the other hand, it provides an opportunity for cleanup. -In the example above, when `coroutine.close` is called the call to `Coroutine.wakeUp` inside the coroutine will throw `VeniceError.canceled` and then the `defer` statement will run, thus releasing the memory allocated for `resource`. +In the example above, when `coroutine.cancel` is called the call to `Coroutine.wakeUp` inside the coroutine will throw `VeniceError.canceledCoroutine` and then the `defer` statement will run, thus releasing the memory allocated for `resource`. # Threads diff --git a/Sources/Venice/Channel.swift b/Sources/Venice/Channel.swift index a6851b3..3609315 100644 --- a/Sources/Venice/Channel.swift +++ b/Sources/Venice/Channel.swift @@ -8,20 +8,6 @@ import CLibdill /// A channel is a synchronization primitive. /// -/// # Threads -/// -/// You can use Venice in multi-threaded programs. -/// However, individual threads are strictly separated. -/// You may think of each thread as a separate process. -/// -/// In particular, a coroutine created in a thread will -/// be executed in that same thread, and it will never -/// migrate to a different one. -/// -/// In a similar manner, a handle, such as a channel or -/// a coroutine handle, created in one thread cannot be -/// used in a different thread. -/// /// ## Example: /// /// ```swift @@ -32,9 +18,10 @@ import CLibdill /// } /// /// let theAnswer = try channel.receive(deadline: 1.second.fromNow()) -/// try coroutine.close() /// ``` -public final class Channel : Handle { +public final class Channel { + private typealias Handle = Int32 + private enum ChannelResult { case value(Type) case error(Error) @@ -49,6 +36,7 @@ public final class Channel : Handle { } } + private let handle: Handle private var buffer = List>() /// Creates a channel @@ -58,20 +46,17 @@ public final class Channel : Handle { /// It doesn't store any items. /// /// - Throws: The following errors might be thrown: - /// #### VeniceError.canceled - /// Thrown when the operation is performed within a closed coroutine. + /// #### VeniceError.canceledCoroutine + /// Thrown when the operation is performed within a canceled coroutine. /// #### VeniceError.outOfMemory - /// Thrown when the system doesn't have enough memory to perform the operation. - /// #### VeniceError.unexpectedError - /// Thrown when an unexpected error occurs. - /// This should never happen in the regular flow of an application. + /// Thrown when the system doesn't have enough memory to create a new channel. public init() throws { let result = chmake(0) guard result != -1 else { switch errno { case ECANCELED: - throw VeniceError.canceled + throw VeniceError.canceledCoroutine case ENOMEM: throw VeniceError.outOfMemory default: @@ -79,18 +64,18 @@ public final class Channel : Handle { } } - super.init(handle: result) + handle = result } deinit { - try? close() + hclose(handle) } /// Reference to the channel which can only send. - public lazy var sendOnly: SendOnly = SendOnly(self) + public lazy var sending: Sending = Sending(self) /// Reference to the channel which can only receive. - public lazy var receiveOnly: ReceiveOnly = ReceiveOnly(self) + public lazy var receiving: Receiving = Receiving(self) /// Sends a value to the channel. public func send(_ value: Type, deadline: Deadline) throws { @@ -108,12 +93,10 @@ public final class Channel : Handle { guard result == 0 else { switch errno { - case EBADF: - throw VeniceError.invalidHandle case ECANCELED: - throw VeniceError.canceled + throw VeniceError.canceledCoroutine case EPIPE: - throw VeniceError.handleIsDone + throw VeniceError.doneChannel case ETIMEDOUT: buffer.remove(node) throw VeniceError.deadlineReached @@ -129,12 +112,10 @@ public final class Channel : Handle { guard result == 0 else { switch errno { - case EBADF: - throw VeniceError.invalidHandle case ECANCELED: - throw VeniceError.canceled + throw VeniceError.canceledCoroutine case EPIPE: - throw VeniceError.handleIsDone + throw VeniceError.doneChannel case ETIMEDOUT: throw VeniceError.deadlineReached default: @@ -145,6 +126,16 @@ public final class Channel : Handle { return try buffer.removeFirst().getValue() } + /// This function is used to inform the channel that no more `send` or `receive` should be + /// performed on the channel. + /// + /// - Warning: + /// After `done` is called on a channel, any attempts to `send` or `receive` + /// will result in a `VeniceError.doneChannel` error. + public func done() { + hdone(handle, 0) + } + /// Send-only reference to an existing channel. /// /// ## Example: @@ -152,29 +143,33 @@ public final class Channel : Handle { /// ```swift /// let channel = Channel() /// - /// func send(to channel: Channel.SendOnly) throws { + /// func send(to channel: Channel.Sending) throws { /// try channel.send(42, deadline: 1.second.fromNow()) /// } /// - /// try send(to: channel.sendOnly) + /// try send(to: channel.sending) /// ``` - public final class SendOnly : Handle { + public final class Sending { private let channel: Channel fileprivate init(_ channel: Channel) { self.channel = channel - super.init(handle: channel.handle) } - /// Sends a value to the channel. + /// :nodoc: public func send(_ value: Type, deadline: Deadline) throws { try channel.send(value, deadline: deadline) } - /// Sends an error to the channel. + /// :nodoc: public func send(_ error: Error, deadline: Deadline) throws { try channel.send(error, deadline: deadline) } + + /// :nodoc: + public func done() { + channel.done() + } } /// Receive-only reference to an existing channel. @@ -184,41 +179,100 @@ public final class Channel : Handle { /// ```swift /// let channel = Channel() /// - /// func receive(from channel: Channel.ReceiveOnly) throws { + /// func receive(from channel: Channel.Receiving) throws { /// let value = try channel.receive(deadline: 1.second.fromNow()) /// } /// - /// try receive(from: channel.receiveOnly) + /// try receive(from: channel.receiving) /// ``` - public final class ReceiveOnly : Handle { + public final class Receiving { private let channel: Channel fileprivate init(_ channel: Channel) { self.channel = channel - super.init(handle: channel.handle) } - /// Receives a value from channel. + /// :nodoc: @discardableResult public func receive(deadline: Deadline) throws -> Type { return try channel.receive(deadline: deadline) } + + /// :nodoc: + public func done() { + channel.done() + } } } extension Channel where Type == Void { - /// Sends to the channel. - /// /// :nodoc: public func send(deadline: Deadline) throws { try send((), deadline: deadline) } } -extension Channel.SendOnly where Type == Void { - /// Sends to the channel. - /// +extension Channel.Sending where Type == Void { /// :nodoc: public func send(deadline: Deadline) throws { try send((), deadline: deadline) } } + +class Node { + var value: T + var next: Node? + weak var previous: Node? + + init(value: T) { + self.value = value + } +} + +fileprivate class List { + private var head: Node? + private var tail: Node? + + @discardableResult fileprivate func append(_ value: T) -> Node { + let newNode = Node(value: value) + + if let tailNode = tail { + newNode.previous = tailNode + tailNode.next = newNode + } else { + head = newNode + } + + tail = newNode + return newNode + } + + @discardableResult fileprivate func remove(_ node: Node) -> T { + let prev = node.previous + let next = node.next + + if let prev = prev { + prev.next = next + } else { + head = next + } + + next?.previous = prev + + if next == nil { + tail = prev + } + + node.previous = nil + node.next = nil + + return node.value + } + + @discardableResult fileprivate func removeFirst() throws -> T { + guard let head = head else { + throw VeniceError.unexpectedError + } + + return remove(head) + } +} diff --git a/Sources/Venice/Coroutine.swift b/Sources/Venice/Coroutine.swift index 642e0c9..13e69c0 100644 --- a/Sources/Venice/Coroutine.swift +++ b/Sources/Venice/Coroutine.swift @@ -22,7 +22,7 @@ import CLibdill /// Coroutines are scheduled cooperatively. What that means is that a coroutine has to /// explicitly yield control of the CPU to allow a different coroutine to run. /// In a typical scenario, this is done transparently to the user: When a coroutine -/// invokes a function that would block (such as `Coroutine.wakeUp`, `fileDescriptor.poll`, `channel.send` or `channel.receive`), +/// invokes a function that would block (such as `Coroutine.wakeUp`, `FileDescriptor.poll`, `channel.send` or `channel.receive`), /// the CPU is automatically yielded. /// However, if a coroutine runs without calling any blocking functions, it may hold /// the CPU forever. For these cases, the `Coroutine.yield` function can be used to manually relinquish @@ -35,9 +35,12 @@ import CLibdill /// ... /// } /// -/// try coroutine.close() +/// coroutine.cancel() /// ``` -public class Coroutine : Handle { +public final class Coroutine { + private typealias Handle = Int32 + private let handle: Handle + /// Launches a coroutine that executes the closure passed as argument. /// The coroutine is executed concurrently, and its lifetime may exceed the lifetime /// of the caller. @@ -49,25 +52,22 @@ public class Coroutine : Handle { /// ... /// } /// - /// try coroutine.close() + /// coroutine.cancel() /// ``` /// /// - Parameters: /// - body: Body of the newly created coroutine. /// /// - Throws: The following errors might be thrown: - /// #### VeniceError.canceled - /// Thrown when the operation is performed within a closed coroutine. + /// #### VeniceError.canceledCoroutine + /// Thrown when the operation is performed within a canceled coroutine. /// #### VeniceError.outOfMemory - /// Thrown when the system doesn't have enough memory to perform the operation. - /// #### VeniceError.unexpectedError - /// Thrown when an unexpected error occurs. - /// This should never happen in the regular flow of an application. + /// Thrown when the system doesn't have enough memory to create a new coroutine. public init(body: @escaping () throws -> Void) throws { var coroutine = { do { try body() - } catch VeniceError.canceled { + } catch VeniceError.canceledCoroutine { return } catch { print(error) @@ -81,7 +81,7 @@ public class Coroutine : Handle { guard result != -1 else { switch errno { case ECANCELED: - throw VeniceError.canceled + throw VeniceError.canceledCoroutine case ENOMEM: throw VeniceError.outOfMemory default: @@ -89,11 +89,20 @@ public class Coroutine : Handle { } } - super.init(handle: result) + handle = result } deinit { - try? close() + cancel() + } + + /// Cancels the coroutine. + /// + /// - Warning: + /// Once a coroutine is canceled any coroutine-blocking operation within the coroutine + /// will throw `VeniceError.canceledCoroutine`. + public func cancel() { + hclose(handle) } /// Explicitly passes control to other coroutines. @@ -111,19 +120,20 @@ public class Coroutine : Handle { /// } /// ``` /// + /// - Warning: + /// Once a coroutine is canceled calling `Couroutine.yield` + /// will throw `VeniceError.canceledCoroutine`. + /// /// - Throws: The following errors might be thrown: - /// #### VeniceError.canceled - /// Thrown when the operation is performed within a closed coroutine. - /// #### VeniceError.unexpectedError - /// Thrown when an unexpected error occurs. - /// This should never happen in the regular flow of an application. + /// #### VeniceError.canceledCoroutine + /// Thrown when the operation is performed within a canceled coroutine. public static func yield() throws { let result = CLibdill.yield() guard result == 0 else { switch errno { case ECANCELED: - throw VeniceError.canceled + throw VeniceError.canceledCoroutine default: throw VeniceError.unexpectedError } @@ -131,20 +141,41 @@ public class Coroutine : Handle { } /// Wakes up at deadline. + /// + /// ## Example: + /// + /// ```swift + /// func execute(_ deadline: Deadline, body: (Void) throws -> R) throws -> R { + /// try Coroutine.wakeUp(deadline) + /// try body() + /// } + /// + /// try execute(1.second.fromNow()) { + /// print("Hey! Ho! Let's go!") + /// } + /// ``` + /// + /// - Warning: + /// Once a coroutine is canceled calling `Couroutine.wakeUp` + /// will throw `VeniceError.canceledCoroutine`. + /// + /// - Throws: The following errors might be thrown: + /// #### VeniceError.canceledCoroutine + /// Thrown when the operation is performed within a canceled coroutine. public static func wakeUp(_ deadline: Deadline) throws { let result = msleep(deadline.value) guard result == 0 else { switch errno { case ECANCELED: - throw VeniceError.canceled + throw VeniceError.canceledCoroutine default: throw VeniceError.unexpectedError } } } - /// Coroutine groups are useful for closing multiple coroutines at the + /// Coroutine groups are useful for canceling multiple coroutines at the /// same time. /// /// ## Example: @@ -159,8 +190,8 @@ public class Coroutine : Handle { /// ... /// } /// - /// // all coroutines in the group will be closed - /// try group.close() + /// // all coroutines in the group will be canceled + /// group.cancel() /// ``` public class Group { private var coroutines: [Int: Coroutine] @@ -201,8 +232,8 @@ public class Coroutine : Handle { /// ... /// } /// - /// // all coroutines in the group will be closed - /// try group.close() + /// // all coroutines in the group will be canceled + /// group.cancel() /// ``` /// /// - Parameter minimumCapacity: The minimum number of elements that the @@ -213,7 +244,7 @@ public class Coroutine : Handle { } deinit { - try? close() + cancel() } /// Creates a lightweight coroutine and adds it to the group. @@ -230,13 +261,10 @@ public class Coroutine : Handle { /// - body: Body of the newly created coroutine. /// /// - Throws: The following errors might be thrown: - /// #### VeniceError.canceled - /// Thrown when the operation is performed within a closed coroutine. + /// #### VeniceError.canceledCoroutine + /// Thrown when the operation is performed within a canceled coroutine. /// #### VeniceError.outOfMemory - /// Thrown when the system doesn't have enough memory to perform the operation. - /// #### VeniceError.unexpectedError - /// Thrown when an unexpected error occurs. - /// This should never happen in the regular flow of an application. + /// Thrown when the system doesn't have enough memory to create a new coroutine. /// - Returns: Newly created coroutine @discardableResult public func addCoroutine(body: @escaping () throws -> Void) throws -> Coroutine { removeFinishedCoroutines() @@ -260,20 +288,12 @@ public class Coroutine : Handle { return coroutine } - /// Closes all coroutines in the group. + /// Cancels all coroutines in the group. /// /// - Warning: - /// `close` guarantees that all associated resources are deallocated. - /// However, it does not guarantee that the coroutines' work will have been fully finished. - /// For example, outbound network data may not be flushed. - /// - /// - Throws: The following errors might be thrown: - /// #### VeniceError.canceled - /// Thrown when the operation is performed on a closed coroutine. - /// #### VeniceError.unexpectedError - /// Thrown when an unexpected error occurs. - /// This should never happen in the regular flow of an application. - public func close() throws { + /// Once a coroutine is canceled any coroutine-blocking operation within the coroutine + /// will throw `VeniceError.canceledCoroutine`. + public func cancel() { removeFinishedCoroutines() for (id, coroutine) in coroutines { @@ -281,7 +301,7 @@ public class Coroutine : Handle { coroutines[id] = nil } - try coroutine.close() + coroutine.cancel() } } diff --git a/Sources/Venice/Error.swift b/Sources/Venice/Error.swift index 84edf80..ab81f1c 100644 --- a/Sources/Venice/Error.swift +++ b/Sources/Venice/Error.swift @@ -1,11 +1,7 @@ /// Venice operation error public enum VeniceError : Error, Equatable { - /// Thrown when the operation is performed on a closed handle. - case canceled - /// Thrown when the operation is not supported. - case operationNotSupported - /// Thrown when the operation is performed on an invalid handle. - case invalidHandle + /// Thrown when the operation is performed within a canceled coroutine. + case canceledCoroutine /// Thrown when the operation is performed on an invalid file descriptor. case invalidFileDescriptor /// Thrown when another coroutine is already blocked on `poll` with this file descriptor. @@ -14,14 +10,12 @@ public enum VeniceError : Error, Equatable { case deadlineReached /// Thrown when the system doesn't have enough memory to perform the operation. case outOfMemory - /// Thrown when the operation is performed on an done handle. - case handleIsDone - /// Thrown when the operation is performed on a broken connection. - case brokenConnection - /// Thrown when the operation is performed on a closed connection. - case closedConnection - /// Thrown when the operation is performed with invalid arguments. - case invalidArguments + /// Thrown when the operation is performed on an done channel. + case doneChannel + /// Thrown when a read operation fails. + case readFailed + /// Thrown when a write operation fails. + case writeFailed /// Thrown when an unexpected error occurs. /// This should never happen in the regular flow of an application. @@ -30,11 +24,7 @@ public enum VeniceError : Error, Equatable { /// :nodoc: public static func == (lhs: VeniceError, rhs: VeniceError) -> Bool { switch (lhs, rhs) { - case (.canceled, .canceled): - return true - case (.operationNotSupported, .operationNotSupported): - return true - case (.invalidHandle, .invalidHandle): + case (.canceledCoroutine, .canceledCoroutine): return true case (.invalidFileDescriptor, .invalidFileDescriptor): return true @@ -44,15 +34,13 @@ public enum VeniceError : Error, Equatable { return true case (.outOfMemory, .outOfMemory): return true - case (.handleIsDone, .handleIsDone): - return true - case (.closedConnection, .closedConnection): + case (.doneChannel, .doneChannel): return true - case (.brokenConnection, .brokenConnection): + case (.unexpectedError, .unexpectedError): return true - case (.invalidArguments, .invalidArguments): + case (.readFailed, .readFailed): return true - case (.unexpectedError, .unexpectedError): + case (.writeFailed, .writeFailed): return true default: return false diff --git a/Sources/Venice/FileDescriptor.swift b/Sources/Venice/FileDescriptor.swift index 9b0ed5a..9865e78 100644 --- a/Sources/Venice/FileDescriptor.swift +++ b/Sources/Venice/FileDescriptor.swift @@ -6,26 +6,35 @@ import CLibdill -/// A handle used to access a file or other input/output resource, +/// A file descriptor used to access a file or other input/output resource, /// such as a pipe or network socket. public final class FileDescriptor { /// File descriptor handle. - public private(set) var handle: Int32 + public typealias Handle = Int32 + + /// File descriptor handle. + public private(set) var handle: Handle + + /// Standard input file descriptor + public static var standardInput = try! FileDescriptor(STDIN_FILENO) + + /// Standard output file descriptor + public static var standardOutput = try! FileDescriptor(STDOUT_FILENO) + + /// Standard error file descriptor + public static var standardError = try! FileDescriptor(STDERR_FILENO) - /// Creates a `FileDescriptor` from a file descriptor handle + /// Creates a `FileDescriptor` from a file descriptor handle. + /// + /// - Warning: + /// This operation will configure the file descriptor as non-blocking. /// /// - Parameters: /// - fileDescriptor: Previously opened file descriptor. - public init(handle: Int32) { - self.handle = handle - } - - /// Configures the `FileDescriptor` to non-blocking - /// /// - Throws: The following errors might be thrown: /// #### VeniceError.invalidFileDescriptor - /// Thrown when `handle` is not an open file descriptor. - public func setNonblocking() throws { + /// Thrown when the operation is performed on an invalid file descriptor. + public init(_ handle: Handle) throws { let flags = fcntl(handle, F_GETFL, 0) guard flags != -1 else { @@ -35,12 +44,152 @@ public final class FileDescriptor { guard fcntl(handle, F_SETFL, flags | O_NONBLOCK) == 0 else { throw VeniceError.invalidFileDescriptor } + + self.handle = handle } deinit { try? close() } + /// Reads from the file descriptor. + /// + /// - Parameters: + /// - buffer: Buffer in which the data will be read to. + /// - deadline: `deadline` is a point in time when the operation should timeout. + /// Use the `.fromNow()` function to get the current point in time. + /// Use `.immediate` if the operation needs to be performed without blocking. + /// Use `.never` to allow the operation to block forever if needed. + /// + /// - Returns: Buffer containing the amount of bytes read. + /// + /// - Throws: The following errors might be thrown: + /// #### VeniceError.readFailed + /// Thrown when `read` operation fails. + /// #### VeniceError.invalidFileDescriptor + /// Thrown when `handle` is not an open file descriptor. + public func read( + _ buffer: UnsafeMutableRawBufferPointer, + deadline: Deadline + ) throws -> UnsafeRawBufferPointer { + let handle = try getHandle() + + guard !buffer.isEmpty, let baseAddress = buffer.baseAddress else { + return UnsafeRawBufferPointer(start: nil, count: 0) + } + + loop: while true { + #if os(Linux) + let result = Glibc.read(handle, buffer.baseAddress, buffer.count) + #else + let result = Darwin.read(handle, buffer.baseAddress, buffer.count) + #endif + + guard result != -1 else { + switch errno { + case EWOULDBLOCK, EAGAIN: + try FileDescriptor.poll(handle, event: .read, deadline: deadline) + continue loop + default: + throw VeniceError.readFailed + } + } + + return UnsafeRawBufferPointer(start: baseAddress, count: result) + } + } + + /// Writes to the file descriptor. + /// + /// - Parameters: + /// - buffer: Buffer which will be written to the file descriptor. + /// - deadline: `deadline` is a point in time when the operation should timeout. + /// Use the `.fromNow()` function to get the current point in time. + /// Use `.immediate` if the operation needs to be performed without blocking. + /// Use `.never` to allow the operation to block forever if needed. + /// + /// - Throws: The following errors might be thrown: + /// #### VeniceError.writeFailed + /// Thrown when `write` operation fails. + /// #### VeniceError.invalidFileDescriptor + /// Thrown when `handle` is not an open file descriptor. + public func write(_ buffer: UnsafeRawBufferPointer, deadline: Deadline) throws { + let handle = try getHandle() + var buffer = buffer + + loop: while !buffer.isEmpty { + #if os(Linux) + let result = Glibc.write(handle, buffer.baseAddress, buffer.count) + #else + let result = Darwin.write(handle, buffer.baseAddress, buffer.count) + #endif + + guard result != -1 else { + switch errno { + case EWOULDBLOCK, EAGAIN: + try FileDescriptor.poll(handle, event: .write, deadline: deadline) + continue loop + default: + throw VeniceError.writeFailed + } + } + + buffer = buffer.suffix(from: result) + } + } + + /// Closes a file descriptor, so that it no longer refers to any + /// file and may be reused. Any record locks held on the + /// file it was associated with, and owned by the process, are removed + /// (regardless of the file descriptor that was used to obtain the lock). + /// + /// - Warning: + /// If `handle` is the last file descriptor referring to the underlying open + /// *file description*, the resources associated with the + /// open file description are freed; if the file descriptor was the last + /// reference to a file which has been removed using `unlink`, the file + /// is deleted. + /// + /// - Throws: The following errors might be thrown: + /// #### VeniceError.invalidFileDescriptor + /// Thrown when `handle` is not an open file descriptor. + public func close() throws { + let handle = try detach() + + #if os(Linux) + guard Glibc.close(handle) == 0 else { + throw VeniceError.invalidFileDescriptor + } + #else + guard Darwin.close(handle) == 0 else { + throw VeniceError.invalidFileDescriptor + } + #endif + } + + /// Detaches the underlying `handle`. + /// After `detach` any operation on the `FileDescriptor` will throw an error. + /// + /// - Returns: The underlying file descriptor. + @discardableResult public func detach() throws -> Handle { + let handle = try getHandle() + + defer { + self.handle = -1 + } + + FileDescriptor.clean(handle) + return handle + } + + private func getHandle() throws -> Handle { + guard handle != -1 else { + throw VeniceError.invalidFileDescriptor + } + + return handle + } + /// Waits for the file descriptor to become either readable/writable /// or to get into an error state. Either case leads to a successful return /// from the function. To distinguish the two outcomes, follow up with a @@ -58,16 +207,13 @@ public final class FileDescriptor { /// - Throws: The following errors might be thrown: /// #### VeniceError.invalidFileDescriptor /// Thrown when the operation is performed on an invalid file descriptor. - /// #### VeniceError.canceled - /// Thrown when the operation is performed within a closed coroutine. + /// #### VeniceError.canceledCoroutine + /// Thrown when the operation is performed within a canceled coroutine. /// #### VeniceError.fileDescriptorBlockedInAnotherCoroutine /// Thrown when another coroutine is already blocked on `poll` with this file descriptor. /// #### VeniceError.deadlineReached /// Thrown when the operation reaches the deadline. - /// #### VeniceError.unexpectedError - /// Thrown when an unexpected error occurs. - /// This should never happen in the regular flow of an application. - public func poll(event: PollEvent, deadline: Deadline) throws { + public static func poll(_ handle: Handle, event: PollEvent, deadline: Deadline) throws { let result: Int32 switch event { @@ -82,7 +228,7 @@ public final class FileDescriptor { case EBADF: throw VeniceError.invalidFileDescriptor case ECANCELED: - throw VeniceError.canceled + throw VeniceError.canceledCoroutine case EBUSY: throw VeniceError.fileDescriptorBlockedInAnotherCoroutine case ETIMEDOUT: @@ -102,7 +248,7 @@ public final class FileDescriptor { /// `clean` has to be called with file descriptors provided by /// third-party libraries, just before returning them back to /// their original owners. Otherwise the behavior is **undefined**. - public func clean() { + public static func clean(_ handle: Handle) { guard handle != -1 else { return } @@ -110,49 +256,6 @@ public final class FileDescriptor { fdclean(handle) } - /// Closes a file descriptor, so that it no longer refers to any - /// file and may be reused. Any record locks held on the - /// file it was associated with, and owned by the process, are removed - /// (regardless of the file descriptor that was used to obtain the lock). - /// - /// - Warning: - /// If `handle` is the last file descriptor referring to the underlying open - /// file description, the resources associated with the - /// open file description are freed; if the file descriptor was the last - /// reference to a file which has been removed using `unlink`, the file - /// is deleted. - /// - /// - Throws: The following errors might be thrown: - /// #### VeniceError.invalidFileDescriptor - /// Thrown when `handle` is not an open file descriptor. - public func close() throws { - clean() - - #if os(Linux) - guard Glibc.close(handle) == 0 else { - throw VeniceError.invalidFileDescriptor - } - #else - guard Darwin.close(handle) == 0 else { - throw VeniceError.invalidFileDescriptor - } - #endif - } - - /// Detaches the underlying `handle`. - /// After `detach` any operation will throw an error. - /// - /// - Returns: The underlying file descriptor. - @discardableResult public func detach() -> Int32 { - clean() - - defer { - handle = -1 - } - - return handle - } - /// Event used to poll file descriptors for reading or writing. public enum PollEvent { /// Event which represents when data is available diff --git a/Sources/Venice/Handle.swift b/Sources/Venice/Handle.swift deleted file mode 100644 index 64edd47..0000000 --- a/Sources/Venice/Handle.swift +++ /dev/null @@ -1,99 +0,0 @@ -#if os(Linux) - import Glibc -#else - import Darwin.C -#endif - -import CLibdill - -public typealias HandleDescriptor = Int32 - -/// Representation of a Venice resource like `Coroutine` and `Channel`. -open class Handle { - /// Raw handle representing the resource. - public var handle: HandleDescriptor - - /// Initializes `Handle` with the raw handle. - /// - /// - Parameter handle: Raw handle representing the resource. - public init(handle: HandleDescriptor) { - self.handle = handle - } - - /// This function is used to inform the handle that there will be no more input. - /// This gives it time to finish it's work and possibly inform the user when it is - /// safe to close the handle. - /// - /// For example, in case of TCP protocol handle, hdone sends out a FIN packet. - /// However, it does not wait until it is acknowledged by the peer. - /// - /// - Warning: - /// After `done` is called on a handle, any attempts to send more data to the handle - /// will result in a `VeniceError.handleIsDone` error. - /// - Warning: - /// Handle implementation may also decide to prevent any further receiving of data - /// and return `VeniceError.handleIsDone` error instead. - /// - /// - Parameters: - /// - deadline: `deadline` is a point in time when the operation should timeout. - /// Use the `.fromNow()` function to get the current point in time. - /// Use `.immediate` if the operation needs to be performed without blocking. - /// Use `.never` to allow the operation to block forever if needed. - /// - /// - Throws: The following errors might be thrown: - /// #### VeniceError.invalidHandle - /// Thrown when the operation is performed on an invalid handle. - /// #### VeniceError.operationNotSupported - /// Thrown when the operation is not supported. - /// #### VeniceError.handleIsDone - /// Thrown when the operation is performed on an done handle. - /// #### VeniceError.deadlineReached - /// Thrown when the operation reaches the deadline. - /// #### VeniceError.unexpectedError - /// Thrown when an unexpected error occurs. - /// This should never happen in the regular flow of an application. - open func done(deadline: Deadline) throws { - let result = hdone(handle, deadline.value) - - guard result == 0 else { - switch errno { - case EBADF: - throw VeniceError.invalidHandle - case ENOTSUP: - throw VeniceError.operationNotSupported - case EPIPE: - throw VeniceError.handleIsDone - case ETIMEDOUT: - throw VeniceError.deadlineReached - default: - throw VeniceError.unexpectedError - } - } - } - - /// Closes the handle. - /// - /// - Warning: - /// `close` guarantees that all associated resources are deallocated. - /// However, it does not guarantee that the handle's work will have been fully finished. - /// For example, outbound network data may not be flushed. - /// - /// - Throws: The following errors might be thrown: - /// #### VeniceError.invalidHandle - /// Thrown when the operation is performed on a invalid handle. - /// #### VeniceError.unexpectedError - /// Thrown when an unexpected error occurs. - /// This should never happen in the regular flow of an application. - open func close() throws { - let result = hclose(handle) - - guard result == 0 else { - switch errno { - case EBADF: - throw VeniceError.invalidHandle - default: - throw VeniceError.unexpectedError - } - } - } -} diff --git a/Sources/Venice/List.swift b/Sources/Venice/List.swift deleted file mode 100644 index fc1ee7e..0000000 --- a/Sources/Venice/List.swift +++ /dev/null @@ -1,58 +0,0 @@ -class Node { - var value: T - var next: Node? - weak var previous: Node? - - init(value: T) { - self.value = value - } -} - -class List { - private var head: Node? - private var tail: Node? - - @discardableResult func append(_ value: T) -> Node { - let newNode = Node(value: value) - - if let tailNode = tail { - newNode.previous = tailNode - tailNode.next = newNode - } else { - head = newNode - } - - tail = newNode - return newNode - } - - @discardableResult func remove(_ node: Node) -> T { - let prev = node.previous - let next = node.next - - if let prev = prev { - prev.next = next - } else { - head = next - } - - next?.previous = prev - - if next == nil { - tail = prev - } - - node.previous = nil - node.next = nil - - return node.value - } - - @discardableResult func removeFirst() throws -> T { - guard let head = head else { - throw VeniceError.unexpectedError - } - - return remove(head) - } -} diff --git a/Tests/VeniceTests/Venice/ChannelTests.swift b/Tests/VeniceTests/Venice/ChannelTests.swift index ba90885..4393495 100644 --- a/Tests/VeniceTests/Venice/ChannelTests.swift +++ b/Tests/VeniceTests/Venice/ChannelTests.swift @@ -10,34 +10,10 @@ public class ChannelTests : XCTestCase { func testCreationOnCanceledCoroutine() throws { let coroutine = try Coroutine { try Coroutine.yield() - XCTAssertThrowsError(try Channel(), error: VeniceError.canceled) + XCTAssertThrowsError(try Channel(), error: VeniceError.canceledCoroutine) } - try coroutine.close() - } - - func testDoneOnCanceledChannel() throws { - let channel = try Channel() - try channel.close() - - XCTAssertThrowsError(try channel.done(deadline: .immediately), error: VeniceError.invalidHandle) - } - - func testDoneOnDoneChannel() throws { - let channel = try Channel() - try channel.done(deadline: .immediately) - - XCTAssertThrowsError(try channel.done(deadline: .immediately), error: VeniceError.handleIsDone) - } - - func testSendOnCanceledChannel() throws { - let channel = try Channel() - try channel.close() - - XCTAssertThrowsError( - try channel.send(deadline: .never), - error: VeniceError.invalidHandle - ) + coroutine.cancel() } func testSendOnCanceledCoroutine() throws { @@ -46,18 +22,18 @@ public class ChannelTests : XCTestCase { let coroutine = try Coroutine { XCTAssertThrowsError( try channel.send(deadline: .never), - error: VeniceError.canceled + error: VeniceError.canceledCoroutine ) } - try coroutine.close() + coroutine.cancel() } func testSendOnDoneChannel() throws { let channel = try Channel() - try channel.done(deadline: .immediately) + channel.done() - XCTAssertThrowsError(try channel.send(deadline: .never), error: VeniceError.handleIsDone) + XCTAssertThrowsError(try channel.send(deadline: .never), error: VeniceError.doneChannel) } func testSendTimeout() throws { @@ -70,7 +46,7 @@ public class ChannelTests : XCTestCase { } XCTAssertEqual(try channel.receive(deadline: .never), 222) - try coroutine.close() + coroutine.cancel() } func testDoubleSendTimeout() throws { @@ -98,19 +74,9 @@ public class ChannelTests : XCTestCase { XCTAssertEqual(try channel.receive(deadline: .never), 333) - try coroutine1.close() - try coroutine2.close() - try coroutine3.close() - } - - func testReceiveOnCanceledChannel() throws { - let channel = try Channel() - try channel.close() - - XCTAssertThrowsError( - try channel.receive(deadline: .never), - error: VeniceError.invalidHandle - ) + coroutine1.cancel() + coroutine2.cancel() + coroutine3.cancel() } func testReceiveOnCanceledCoroutine() throws { @@ -119,21 +85,17 @@ public class ChannelTests : XCTestCase { let coroutine = try Coroutine { XCTAssertThrowsError( try channel.receive(deadline: .never), - error: VeniceError.canceled + error: VeniceError.canceledCoroutine ) } - try coroutine.close() + coroutine.cancel() } func testReceiveOnDoneChannel() throws { let channel = try Channel() - try channel.done(deadline: .immediately) - - XCTAssertThrowsError( - try channel.receive(deadline: .never), - error: VeniceError.handleIsDone - ) + channel.done() + XCTAssertThrowsError(try channel.receive(deadline: .never), error: VeniceError.doneChannel) } func testReceiveTimeout() throws { @@ -149,7 +111,7 @@ public class ChannelTests : XCTestCase { } try channel.send(222, deadline: .never) - try coroutine.close() + coroutine.cancel() } func testReceiverWaitsForSender() throws { @@ -160,7 +122,7 @@ public class ChannelTests : XCTestCase { } try channel.send(333, deadline: .never) - try coroutine.close() + coroutine.cancel() } func testSenderWaitsForReceiver() throws { @@ -171,53 +133,50 @@ public class ChannelTests : XCTestCase { } XCTAssertEqual(try channel.receive(deadline: .never), 444) - try coroutine.close() + coroutine.cancel() } func testSendingChannel() throws { let channel = try Channel() - func send(to channel: Channel.SendOnly) throws { + func send(to channel: Channel.Sending) throws { try channel.send(111, deadline: .never) } let coroutine = try Coroutine { - try send(to: channel.sendOnly) + try send(to: channel.sending) } XCTAssertEqual(try channel.receive(deadline: .never), 111) - try coroutine.close() + coroutine.cancel() } func testSendErrorToSendingChannel() throws { let channel = try Channel() - func send(to channel: Channel.SendOnly) throws { + func send(to channel: Channel.Sending) throws { try channel.send(VeniceError.unexpectedError, deadline: .never) } let coroutine = try Coroutine { - try send(to: channel.sendOnly) + try send(to: channel.sending) } XCTAssertThrowsError(try channel.receive(deadline: .never), error: VeniceError.unexpectedError) - - try coroutine.close() + coroutine.cancel() } func testDoneOnDoneSendingChannel() throws { let channel = try Channel() - let sending = channel.sendOnly - - try channel.done(deadline: .immediately) - - XCTAssertThrowsError(try sending.done(deadline: .immediately), error: VeniceError.handleIsDone) + let sending = channel.sending + channel.done() + sending.done() } func testReceivingChannel() throws { let channel = try Channel() - func receive(_ channel: Channel.ReceiveOnly) { + func receive(_ channel: Channel.Receiving) { XCTAssertEqual(try channel.receive(deadline: .never), 999) } @@ -225,17 +184,15 @@ public class ChannelTests : XCTestCase { try channel.send(999, deadline: .never) } - receive(channel.receiveOnly) - try coroutine.close() + receive(channel.receiving) + coroutine.cancel() } func testDoneOnDoneReceivingChannel() throws { let channel = try Channel() - let receiving = channel.receiveOnly - - try channel.done(deadline: .immediately) - - XCTAssertThrowsError(try receiving.done(deadline: .immediately), error: VeniceError.handleIsDone) + let receiving = channel.receiving + channel.done() + receiving.done() } func testTwoSimultaneousSenders() throws { @@ -252,8 +209,8 @@ public class ChannelTests : XCTestCase { XCTAssertEqual(try channel.receive(deadline: .never), 888) XCTAssertEqual(try channel.receive(deadline: .never), 999) - try coroutine1.close() - try coroutine2.close() + coroutine1.cancel() + coroutine2.cancel() } func testTwoSimultaneousReceivers() throws { @@ -270,8 +227,8 @@ public class ChannelTests : XCTestCase { try channel.send(333, deadline: .never) try channel.send(444, deadline: .never) - try coroutine1.close() - try coroutine2.close() + coroutine1.cancel() + coroutine2.cancel() } func testTypedChannels() throws { @@ -293,8 +250,8 @@ public class ChannelTests : XCTestCase { XCTAssertEqual(foo.bar, 555) XCTAssertEqual(foo.baz, 222) - try coroutine1.close() - try coroutine2.close() + coroutine1.cancel() + coroutine2.cancel() } func testDoneChannelUnblocks() throws { @@ -304,7 +261,7 @@ public class ChannelTests : XCTestCase { let coroutine1 = try Coroutine { XCTAssertThrowsError( try channel1.receive(deadline: .never), - error: VeniceError.handleIsDone + error: VeniceError.doneChannel ) try channel2.send(0, deadline: .never) @@ -313,19 +270,19 @@ public class ChannelTests : XCTestCase { let coroutine2 = try Coroutine { XCTAssertThrowsError( try channel1.receive(deadline: .never), - error: VeniceError.handleIsDone + error: VeniceError.doneChannel ) try channel2.send(0, deadline: .never) } - try channel1.done(deadline: .immediately) + try channel1.done() XCTAssertEqual(try channel2.receive(deadline: .never), 0) XCTAssertEqual(try channel2.receive(deadline: .never), 0) - try coroutine1.close() - try coroutine2.close() + coroutine1.cancel() + coroutine2.cancel() } func testTenThousandWhispers() throws { @@ -355,8 +312,8 @@ public class ChannelTests : XCTestCase { XCTAssertEqual(try leftmost.receive(deadline: .never), numberOfWhispers + 1) - try starter.close() - try whispers.close() + starter.cancel() + whispers.cancel() } catch { XCTFail() } @@ -368,13 +325,9 @@ extension ChannelTests { public static var allTests: [(String, (ChannelTests) -> () throws -> Void)] { return [ ("testCreationOnCanceledCoroutine", testCreationOnCanceledCoroutine), - ("testDoneOnCanceledChannel", testDoneOnCanceledChannel), - ("testDoneOnDoneChannel", testDoneOnDoneChannel), - ("testSendOnCanceledChannel", testSendOnCanceledChannel), ("testSendOnCanceledCoroutine", testSendOnCanceledCoroutine), ("testSendOnDoneChannel", testSendOnDoneChannel), ("testSendTimeout", testSendTimeout), - ("testReceiveOnCanceledChannel", testReceiveOnCanceledChannel), ("testReceiveOnCanceledCoroutine", testReceiveOnCanceledCoroutine), ("testReceiveOnDoneChannel", testReceiveOnDoneChannel), ("testReceiveTimeout", testReceiveTimeout), diff --git a/Tests/VeniceTests/Venice/CoroutineTests.swift b/Tests/VeniceTests/Venice/CoroutineTests.swift index b5cc1a9..07eaddf 100644 --- a/Tests/VeniceTests/Venice/CoroutineTests.swift +++ b/Tests/VeniceTests/Venice/CoroutineTests.swift @@ -5,7 +5,7 @@ #endif import XCTest -import Venice +@testable import Venice public class CoroutineTests : XCTestCase { func testCoroutine() throws { @@ -33,18 +33,18 @@ public class CoroutineTests : XCTestCase { try Coroutine.wakeUp(100.milliseconds.fromNow()) XCTAssertEqual(sum, 42) - try coroutine1.close() - try coroutine2.close() - try coroutine3.close() + coroutine1.cancel() + coroutine2.cancel() + coroutine3.cancel() } func testCoroutineOnCanceledCoroutine() throws { let coroutine = try Coroutine { try Coroutine.yield() - XCTAssertThrowsError(try Coroutine(body: {}), error: VeniceError.canceled) + XCTAssertThrowsError(try Coroutine(body: {}), error: VeniceError.canceledCoroutine) } - try coroutine.close() + coroutine.cancel() } func testThrowOnCoroutine() throws { @@ -56,34 +56,34 @@ public class CoroutineTests : XCTestCase { throw NiceError(description: "NICE™") } - try coroutine.close() + coroutine.cancel() } func testYiedOnCanceledCoroutine() throws { let coroutine = try Coroutine { try Coroutine.yield() - XCTAssertThrowsError(try Coroutine.yield(), error: VeniceError.canceled) + XCTAssertThrowsError(try Coroutine.yield(), error: VeniceError.canceledCoroutine) } - try coroutine.close() + coroutine.cancel() } -// func testWakeUp() throws { -// let deadline = 100.milliseconds.fromNow() -// try Coroutine.wakeUp(deadline) -// let difference = Deadline.now().value - deadline.value -// XCTAssert(difference > -100.milliseconds.value && difference < 100.milliseconds.value) -// } + func testWakeUp() throws { + let deadline = 100.milliseconds.fromNow() + try Coroutine.wakeUp(deadline) + let difference = Deadline.now().value - deadline.value + XCTAssert(difference > -100.milliseconds.value && difference < 100.milliseconds.value) + } func testWakeUpOnCanceledCoroutine() throws { let coroutine = try Coroutine { XCTAssertThrowsError( try Coroutine.wakeUp(100.milliseconds.fromNow()), - error: VeniceError.canceled + error: VeniceError.canceledCoroutine ) } - try coroutine.close() + coroutine.cancel() } func testWakeUpWithChannels() throws { @@ -105,40 +105,67 @@ public class CoroutineTests : XCTestCase { XCTAssert(try channel.receive(deadline: .never) == 111) XCTAssert(try channel.receive(deadline: .never) == 222) - try group.close() + group.cancel() } func testPollFileDescriptor() throws { + let deadline = 1.second.fromNow() let (socket1, socket2) = try createSocketPair() - try socket1.poll(event: .write, deadline: 100.milliseconds.fromNow()) - try socket1.poll(event: .write, deadline: 100.milliseconds.fromNow()) + try socket1.poll(event: .write, deadline: deadline) + try socket1.poll(event: .write, deadline: deadline) XCTAssertThrowsError( - try socket1.poll(event: .read, deadline: 100.milliseconds.fromNow()), + try socket1.poll(event: .read, deadline: deadline), error: VeniceError.deadlineReached ) var size = send(socket2.handle, "A", 1, 0) - XCTAssert(size == 1) + XCTAssertEqual(size, 1) - try socket1.poll(event: .write, deadline: 100.milliseconds.fromNow()) - try socket1.poll(event: .read, deadline: 100.milliseconds.fromNow()) + try socket1.poll(event: .write, deadline: deadline) + try socket1.poll(event: .read, deadline: deadline) var character: Int8 = 0 size = recv(socket1.handle, &character, 1, 0) - XCTAssert(size == 1) - XCTAssert(character == 65) + XCTAssertEqual(size, 1) + XCTAssertEqual(character, 65) + } + + func testReadWriteFileDescriptor() throws { + let deadline = 1.second.fromNow() + let (socket1, socket2) = try createSocketPair() + + let socket1Buffer = UnsafeMutableRawBufferPointer.allocate(count: 1) + let socket2Buffer = UnsafeMutableRawBufferPointer.allocate(count: 1) + + defer { + socket1Buffer.deallocate() + socket2Buffer.deallocate() + } + + var read: UnsafeRawBufferPointer + + socket1Buffer[0] = 42 + socket2Buffer[0] = 0 + try socket1.write(UnsafeRawBufferPointer(socket1Buffer), deadline: deadline) + read = try socket2.read(socket2Buffer, deadline: deadline) + XCTAssertEqual(read[0], 42) + XCTAssertEqual(socket1Buffer[0], 42) + XCTAssertEqual(socket2Buffer[0], 42) + + socket1Buffer[0] = 0 + socket2Buffer[0] = 69 + try socket2.write(UnsafeRawBufferPointer(socket2Buffer), deadline: deadline) + read = try socket1.read(socket1Buffer, deadline: deadline) + XCTAssertEqual(read[0], 69) + XCTAssertEqual(socket1Buffer[0], 69) + XCTAssertEqual(socket2Buffer[0], 69) } func testInvalidFileDescriptor() throws { - let fd = FileDescriptor(handle: -1) - - XCTAssertThrowsError( - try fd.setNonblocking(), - error: VeniceError.invalidFileDescriptor - ) + XCTAssertThrowsError(try FileDescriptor(-1), error: VeniceError.invalidFileDescriptor) } func testPollOnCanceledCoroutine() throws { @@ -147,11 +174,11 @@ public class CoroutineTests : XCTestCase { let coroutine = try Coroutine { XCTAssertThrowsError( try socket1.poll(event: .read, deadline: .never), - error: VeniceError.canceled + error: VeniceError.canceledCoroutine ) } - try coroutine.close() + coroutine.cancel() } func testFileDescriptorBlockedInAnotherCoroutine() throws { @@ -160,7 +187,7 @@ public class CoroutineTests : XCTestCase { let coroutine1 = try Coroutine { XCTAssertThrowsError( try socket1.poll(event: .read, deadline: .never), - error: VeniceError.canceled + error: VeniceError.canceledCoroutine ) } @@ -171,13 +198,8 @@ public class CoroutineTests : XCTestCase { ) } - try coroutine1.close() - try coroutine2.close() - } - - func testCleanFileDescriptor() throws { - let fileDescriptor = FileDescriptor(handle: STDIN_FILENO) - fileDescriptor.clean() + coroutine1.cancel() + coroutine2.cancel() } func testDetachFileDescriptor() throws { @@ -191,9 +213,9 @@ public class CoroutineTests : XCTestCase { XCTAssert(result == 0) - let fileDescriptor = FileDescriptor(handle: sockets[0]) - let standardInput = fileDescriptor.detach() - XCTAssertEqual(standardInput, sockets[0]) + let fileDescriptor = try FileDescriptor(sockets[0]) + let socket = try fileDescriptor.detach() + XCTAssertEqual(socket, sockets[0]) XCTAssertEqual(fileDescriptor.handle, -1) XCTAssertThrowsError( @@ -201,6 +223,16 @@ public class CoroutineTests : XCTestCase { error: VeniceError.invalidFileDescriptor ) } + + func testStandardStreams() { + let input = FileDescriptor.standardInput + let output = FileDescriptor.standardOutput + let error = FileDescriptor.standardError + + XCTAssertEqual(try input.detach(), STDIN_FILENO) + XCTAssertEqual(try output.detach(), STDOUT_FILENO) + XCTAssertEqual(try error.detach(), STDERR_FILENO) + } } func createSocketPair() throws -> (FileDescriptor, FileDescriptor) { @@ -213,8 +245,7 @@ func createSocketPair() throws -> (FileDescriptor, FileDescriptor) { #endif XCTAssert(result == 0) - - return (FileDescriptor(handle: sockets[0]), FileDescriptor(handle: sockets[1])) + return try (FileDescriptor(sockets[0]), FileDescriptor(sockets[1])) } extension CoroutineTests { @@ -224,14 +255,16 @@ extension CoroutineTests { ("testCoroutineOnCanceledCoroutine", testCoroutineOnCanceledCoroutine), ("testThrowOnCoroutine", testThrowOnCoroutine), ("testYiedOnCanceledCoroutine", testYiedOnCanceledCoroutine), -// ("testWakeUp", testWakeUp), + ("testWakeUp", testWakeUp), ("testWakeUpOnCanceledCoroutine", testWakeUpOnCanceledCoroutine), ("testWakeUpWithChannels", testWakeUpWithChannels), ("testPollFileDescriptor", testPollFileDescriptor), + ("testReadWriteFileDescriptor", testReadWriteFileDescriptor), ("testInvalidFileDescriptor", testInvalidFileDescriptor), ("testPollOnCanceledCoroutine", testPollOnCanceledCoroutine), ("testFileDescriptorBlockedInAnotherCoroutine", testFileDescriptorBlockedInAnotherCoroutine), - ("testCleanFileDescriptor", testCleanFileDescriptor), + ("testDetachFileDescriptor", testDetachFileDescriptor), + ("testStandardStreams", testStandardStreams), ] } } diff --git a/Tests/VeniceTests/Venice/TimeTests.swift b/Tests/VeniceTests/Venice/TimeTests.swift index 19f04ea..13cc8c9 100644 --- a/Tests/VeniceTests/Venice/TimeTests.swift +++ b/Tests/VeniceTests/Venice/TimeTests.swift @@ -3,10 +3,10 @@ import Venice public class TimeTests : XCTestCase { func testTime() throws { - XCTAssertEqual(1.millisecond, 1.millisecond) - XCTAssertEqual(1000.millisecond, 1.second) - XCTAssertEqual(60000.millisecond, 1.minute) - XCTAssertEqual(3600000.millisecond, 1.hour) + XCTAssertEqual(1.millisecond, 1.milliseconds) + XCTAssertEqual(1000.millisecond, 1.seconds) + XCTAssertEqual(60000.millisecond, 1.minutes) + XCTAssertEqual(3600000.millisecond, 1.hours) } } diff --git a/docs/Channels.html b/docs/Channels.html index 076044d..783c5a6 100644 --- a/docs/Channels.html +++ b/docs/Channels.html @@ -27,14 +27,6 @@
- diff --git a/docs/Classes/Coroutine.html b/docs/Classes/Coroutine.html index aceb7d7..2c3c7fd 100644 --- a/docs/Classes/Coroutine.html +++ b/docs/Classes/Coroutine.html @@ -28,14 +28,6 @@
+
  • +
    + + + + cancel() + +
    +
    +
    +
    +
    +
    +

    Cancels the coroutine.

    +
    +

    Warning

    + Once a coroutine is canceled any coroutine-blocking operation within the coroutine +will throw VeniceError.canceledCoroutine. + +
    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func cancel()
    + +
    +
    +
    +
    +
  • @@ -232,14 +253,18 @@

    Example:

    try Coroutine.yield() // Give other coroutines a chance to run. }
    +
    +

    Warning

    +

    Once a coroutine is canceled calling Couroutine.yield +will throw VeniceError.canceledCoroutine.

    + +

    Throws

    - The following errors might be thrown: -#### VeniceError.canceled -Thrown when the operation is performed within a closed coroutine. -#### VeniceError.unexpectedError -Thrown when an unexpected error occurs. -This should never happen in the regular flow of an application. +

    The following errors might be thrown:

    +

    VeniceError.canceledCoroutine

    + +

    Thrown when the operation is performed within a canceled coroutine.

    @@ -269,6 +294,30 @@

    Declaration

    Wakes up at deadline.

    +

    Example:

    +
    func execute<R>(_ deadline: Deadline, body: (Void) throws -> R) throws -> R {
    +    try Coroutine.wakeUp(deadline)
    +    try body()
    +}
    +
    +try execute(1.second.fromNow()) {
    +    print("Hey! Ho! Let's go!")
    +}
    +
    +
    +

    Warning

    +

    Once a coroutine is canceled calling Couroutine.wakeUp +will throw VeniceError.canceledCoroutine.

    + +
    +
    +

    Throws

    +

    The following errors might be thrown:

    +

    VeniceError.canceledCoroutine

    + +

    Thrown when the operation is performed within a canceled coroutine.

    + +
    @@ -295,7 +344,7 @@

    Declaration

    -

    Coroutine groups are useful for closing multiple coroutines at the +

    Coroutine groups are useful for canceling multiple coroutines at the same time.

    Example:

    let group = Coroutine.Group(minimumCapacity: 2)
    @@ -308,8 +357,8 @@ 

    Example:

    ... } -// all coroutines in the group will be closed -try group.close() +// all coroutines in the group will be canceled +group.cancel()
    See more @@ -330,7 +379,7 @@

    Declaration

    diff --git a/docs/Classes/Coroutine/Group.html b/docs/Classes/Coroutine/Group.html index be01b91..7d74450 100644 --- a/docs/Classes/Coroutine/Group.html +++ b/docs/Classes/Coroutine/Group.html @@ -28,14 +28,6 @@
    -

    Coroutine groups are useful for closing multiple coroutines at the +

    Coroutine groups are useful for canceling multiple coroutines at the same time.

    Example:

    let group = Coroutine.Group(minimumCapacity: 2)
    @@ -119,8 +111,8 @@ 

    Example:

    ... } -// all coroutines in the group will be closed -try group.close() +// all coroutines in the group will be canceled +group.cancel()
    @@ -158,8 +150,8 @@

    Example:

    ... } -// all coroutines in the group will be closed -try group.close() +// all coroutines in the group will be canceled +group.cancel()
    @@ -217,16 +209,12 @@

    Example:

    Throws

    The following errors might be thrown:

    -

    VeniceError.canceled

    +

    VeniceError.canceledCoroutine

    -

    Thrown when the operation is performed within a closed coroutine.

    +

    Thrown when the operation is performed within a canceled coroutine.

    VeniceError.outOfMemory

    -

    Thrown when the system doesn’t have enough memory to perform the operation.

    -

    VeniceError.unexpectedError

    - -

    Thrown when an unexpected error occurs. -This should never happen in the regular flow of an application.

    +

    Thrown when the system doesn’t have enough memory to create a new coroutine.

    @@ -268,9 +256,9 @@

    Return Value

  • - - - close() + + + cancel()
    @@ -278,24 +266,11 @@

    Return Value

    -

    Closes all coroutines in the group.

    +

    Cancels all coroutines in the group.

    Warning

    -

    close guarantees that all associated resources are deallocated. -However, it does not guarantee that the coroutines’ work will have been fully finished. -For example, outbound network data may not be flushed.

    - -
    -
    -

    Throws

    -

    The following errors might be thrown:

    -

    VeniceError.canceled

    - -

    Thrown when the operation is performed on a closed coroutine.

    -

    VeniceError.unexpectedError

    - -

    Thrown when an unexpected error occurs. -This should never happen in the regular flow of an application.

    + Once a coroutine is canceled any coroutine-blocking operation within the coroutine +will throw VeniceError.canceledCoroutine.
    @@ -304,7 +279,7 @@

    VeniceError.unexpectedError

    Declaration

    Swift

    -
    public func close() throws
    +
    public func cancel()
    @@ -316,7 +291,7 @@

    Declaration

    diff --git a/docs/Classes/FileDescriptor.html b/docs/Classes/FileDescriptor.html index 290e2f4..a3d122d 100644 --- a/docs/Classes/FileDescriptor.html +++ b/docs/Classes/FileDescriptor.html @@ -28,14 +28,6 @@
    -

    A handle used to access a file or other input/output resource, +

    A file descriptor used to access a file or other input/output resource, such as a pipe or network socket.

    @@ -116,9 +108,9 @@

    FileDescriptor

  • - - - fileDescriptor + + + handle
    @@ -133,7 +125,115 @@

    FileDescriptor

    Declaration

    Swift

    -
    public private(set) var fileDescriptor: Int32
    +
    public private(set) var handle: Handle
    + +
    +
    + + +
  • +
  • +
    + + + + Handle + +
    +
    +
    +
    +
    +
    +

    File descriptor handle.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public typealias Handle = Int32
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + standardInput + +
    +
    +
    +
    +
    +
    +

    Standard input file descriptor

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public static var standardInput = try! FileDescriptor(STDIN_FILENO)
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + standardOutput + +
    +
    +
    +
    +
    +
    +

    Standard output file descriptor

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public static var standardOutput = try! FileDescriptor(STDOUT_FILENO)
    + +
    +
    +
    +
    +
  • +
  • +
    + + + + standardError + +
    +
    +
    +
    +
    +
    +

    Standard error file descriptor

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public static var standardError = try! FileDescriptor(STDERR_FILENO)
    @@ -153,14 +253,18 @@

    Declaration

    -

    Creates a FileDescriptor from a file descriptor handle and -configures it as non-blocking.

    +

    Creates a FileDescriptor from a file descriptor handle.

    +
    +

    Warning

    +

    This operation will configure the file descriptor as non-blocking.

    + +

    Throws

    The following errors might be thrown:

    VeniceError.invalidFileDescriptor

    -

    Thrown when fileDescriptor is not an open file descriptor.

    +

    Thrown when the operation is performed on an invalid file descriptor.

    @@ -169,7 +273,7 @@

    VeniceError.invalidFi

    Declaration

    Swift

    -
    public init(_ fileDescriptor: Int32) throws
    +
    public init(_ handle: Handle) throws
    @@ -198,9 +302,9 @@

    Parameters

  • @@ -208,29 +312,16 @@

    Parameters

    -

    Waits for the file descriptor to become either readable/writable -or to get into an error state. Either case leads to a successful return -from the function. To distinguish the two outcomes, follow up with a -read/write operation on the file descriptor.

    +

    Reads from the file descriptor.

    Throws

    The following errors might be thrown:

    -

    VeniceError.invalidFileDescriptor

    - -

    Thrown when the operation is performed on an invalid file descriptor.

    -

    VeniceError.canceled

    - -

    Thrown when the operation is performed within a closed coroutine.

    -

    VeniceError.fileDescriptorBlockedInAnotherCoroutine

    - -

    Thrown when another coroutine is already blocked on poll with this file descriptor.

    -

    VeniceError.deadlineReached

    +

    VeniceError.readFailed

    -

    Thrown when the operation reaches the deadline.

    -

    VeniceError.unexpectedError

    +

    Thrown when read operation fails.

    +

    VeniceError.invalidFileDescriptor

    -

    Thrown when an unexpected error occurs. -This should never happen in the regular flow of an application.

    +

    Thrown when handle is not an open file descriptor.

    @@ -239,7 +330,10 @@

    VeniceError.unexpectedError

    Declaration

    Swift

    -
    public func poll(event: PollEvent, deadline: Deadline) throws
    +
    public func read(
    +        _ buffer: UnsafeMutableRawBufferPointer,
    +        deadline: Deadline
    +    ) throws -> UnsafeRawBufferPointer
    @@ -250,13 +344,12 @@

    Parameters

    - event + buffer
    -

    Use .read to wait for the file descriptor to become readable. -Use .write to wait for the file descriptor to become writable.

    +

    Buffer in which the data will be read to.

    @@ -278,15 +371,19 @@

    Parameters

    +
    +

    Return Value

    +

    Buffer containing the amount of bytes read.

    +
  • @@ -294,15 +391,16 @@

    Parameters

    -

    Erases cached info about a file descriptor.

    +

    Writes to the file descriptor.

    +
    +

    Throws

    +

    The following errors might be thrown:

    +

    VeniceError.writeFailed

    -

    This function drops any state that Venice associates with -the file descriptor.

    -
    -

    Warning

    - clean has to be called with file descriptors provided by -third-party libraries, just before returning them back to -their original owners. Otherwise the behavior is undefined. +

    Thrown when write operation fails.

    +

    VeniceError.invalidFileDescriptor

    + +

    Thrown when handle is not an open file descriptor.

    @@ -311,10 +409,44 @@

    Parameters

    Declaration

    Swift

    -
    public func clean()
    +
    public func write(_ buffer: UnsafeRawBufferPointer, deadline: Deadline) throws
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + buffer + + +
    +

    Buffer which will be written to the file descriptor.

    +
    +
    + + deadline + + +
    +

    deadline is a point in time when the operation should timeout. +Use the .fromNow() function to get the current point in time. +Use .immediate if the operation needs to be performed without blocking. +Use .never to allow the operation to block forever if needed.

    +
    +
    +
  • @@ -337,8 +469,8 @@

    Declaration

    (regardless of the file descriptor that was used to obtain the lock).

    Warning

    -

    If fileDescriptor is the last file descriptor referring to the underlying open -file description, the resources associated with the +

    If handle is the last file descriptor referring to the underlying open +file description, the resources associated with the open file description are freed; if the file descriptor was the last reference to a file which has been removed using unlink, the file is deleted.

    @@ -349,7 +481,7 @@

    Declaration

    The following errors might be thrown:

    VeniceError.invalidFileDescriptor

    -

    Thrown when fileDescriptor is not an open file descriptor.

    +

    Thrown when handle is not an open file descriptor.

    @@ -368,9 +500,9 @@

    Declaration

  • - + - detach() + detach()
    @@ -378,15 +510,15 @@

    Declaration

    -

    Detaches the underlying fileDescriptor. -After detach any operation will throw an error.

    +

    Detaches the underlying handle. +After detach any operation on the FileDescriptor will throw an error.

    Declaration

    Swift

    -
    @discardableResult public func detach() -> Int32
    +
    @discardableResult public func detach() throws -> Handle
    @@ -397,6 +529,125 @@

    Return Value

  • +
  • + +
    +
    +
    +
    +
    +

    Waits for the file descriptor to become either readable/writable +or to get into an error state. Either case leads to a successful return +from the function. To distinguish the two outcomes, follow up with a +read/write operation on the file descriptor.

    +
    +

    Throws

    +

    The following errors might be thrown:

    +

    VeniceError.invalidFileDescriptor

    + +

    Thrown when the operation is performed on an invalid file descriptor.

    +

    VeniceError.canceledCoroutine

    + +

    Thrown when the operation is performed within a canceled coroutine.

    +

    VeniceError.fileDescriptorBlockedInAnotherCoroutine

    + +

    Thrown when another coroutine is already blocked on poll with this file descriptor.

    +

    VeniceError.deadlineReached

    + +

    Thrown when the operation reaches the deadline.

    + +
    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public static func poll(_ handle: Handle, event: PollEvent, deadline: Deadline) throws
    + +
    +
    +
    +

    Parameters

    + + + + + + + + + + + +
    + + event + + +
    +

    Use .read to wait for the file descriptor to become readable. +Use .write to wait for the file descriptor to become writable.

    +
    +
    + + deadline + + +
    +

    deadline is a point in time when the operation should timeout. +Use the .fromNow() function to get the current point in time. +Use .immediate if the operation needs to be performed without blocking. +Use .never to allow the operation to block forever if needed.

    +
    +
    +
    +
    +
    +
  • +
  • +
    + + + + clean(_:) + +
    +
    +
    +
    +
    +
    +

    Erases cached info about a file descriptor.

    + +

    This function drops any state that Venice associates with +the file descriptor.

    +
    +

    Warning

    + clean has to be called with file descriptors provided by +third-party libraries, just before returning them back to +their original owners. Otherwise the behavior is undefined. + +
    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public static func clean(_ handle: Handle)
    + +
    +
    +
    +
    +
  • @@ -430,7 +681,7 @@

    Declaration

    diff --git a/docs/Classes/FileDescriptor/PollEvent.html b/docs/Classes/FileDescriptor/PollEvent.html index c8dae21..8d1a67a 100644 --- a/docs/Classes/FileDescriptor/PollEvent.html +++ b/docs/Classes/FileDescriptor/PollEvent.html @@ -28,14 +28,6 @@
    - diff --git a/docs/Coroutines.html b/docs/Coroutines.html index ba3d758..8af25b9 100644 --- a/docs/Coroutines.html +++ b/docs/Coroutines.html @@ -27,14 +27,6 @@
    @@ -163,7 +155,7 @@

    Declaration

    diff --git a/docs/Enums/VeniceError.html b/docs/Enums/VeniceError.html index 25ffb9e..be43da9 100644 --- a/docs/Enums/VeniceError.html +++ b/docs/Enums/VeniceError.html @@ -28,14 +28,6 @@
    -
    -
      -
    • -
      - - - - operationNotSupported - -
      -
      -
      -
      -
      -
      -

      Thrown when the operation is not supported.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      case operationNotSupported
      - -
      -
      -
      -
      -
    • -
    -
    -
    -
      -
    • -
      @@ -187,14 +117,14 @@

      Declaration

      -

      Thrown when the operation is performed on an invalid handle.

      +

      Thrown when the operation is performed within a canceled coroutine.

      Declaration

      Swift

      -
      case invalidHandle
      +
      case canceledCoroutine
      @@ -332,40 +262,9 @@

      Declaration

    • - - - handleIsDone - -
      -
      -
      -
      -
      -
      -

      Thrown when the operation is performed on an done handle.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      case handleIsDone
      - -
      -
      -
      -
      -
    • -
    -
    -
    -