blob: 91555cb52985eeb8c08e57056567185108cc3a1e [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import Foundation
public struct Context {
internal var handle:ContextHandle {
didSet {
log.debug("Updated context \(self)")
}
}
private var _isCancelled:Bool = false
private var _isCancellable:Bool = false
private var dirty:ContextFeatures = []
public var userInfo:[String: Any] = [:]
internal init(handle:ContextHandle) {
self.handle = handle
}
internal init(handle:ContextHandle, existingDeadline:NSDate?, isCancellable:Bool) {
self.handle = handle
self.deadline = existingDeadline
self._isCancellable = isCancellable
}
public mutating func client() throws -> Client {
try updateHandleIfNeeded()
return Client(defaultContext: self)
}
public var deadline:NSDate? {
didSet {
guard oldValue != deadline else { return }
guard deadline != nil else {
log.warning("Cannot undo a deadline once one has been set. Instead mutate the root context. This will no-op.")
deadline = oldValue // TODO Verify that the following didSet won't be a problem
return
}
// guard oldValue == nil || deadline!.isBefore(oldValue!) else {
// log.warning("Cannot set a deadline that is after the old value. Instead mutate the root context. This will no-op.")
// deadline = oldValue // TODO Verify that the following didSet won't be a problem
// return
// }
dirty.insert(ContextFeatures.Deadline)
}
}
public var isCancelled:Bool { return _isCancelled }
public var isCancellable:Bool {
get {
return !_isCancelled && (_isCancellable || dirty.contains(ContextFeatures.Cancellable))
}
set {
guard _isCancellable != newValue else { return }
guard newValue == true else {
log.warning("Cannot set something as not cancellable once it already is marked as such. No-op.")
return
}
// This shouldn't be possible given we were marked as not cancellable previously.
guard !_isCancelled else {
log.warning("Already cancelled, so this is a no-op.")
return
}
dirty.insert(ContextFeatures.Cancellable)
}
}
/// Cancels all associated RPC with this context and renders it unusable. Call newContext afterwards
/// to get a similar context that is workable.
///
/// If this context is not cancellable (it has already been cancelled, or a deadline/timeout
/// was never set), then this will throw a ContextError.
///
/// If it does not throw right away, the promise will never fail.
private static let outstandingCancels = GoPromises<Void>(timeout: nil)
public mutating func cancel() throws -> Promise<Void> {
guard !_isCancelled else { throw ContextError.ContextIsAlreadyCancelled }
guard _isCancellable else { throw ContextError.ContextNotCancellable }
_isCancelled = true
let (asyncId, p) = Context.outstandingCancels.newPromise()
swift_io_v_v23_context_CancelableVContext_nativeCancelAsync(handle.goHandle, asyncId, { asyncId in
if let p = Context.outstandingCancels.getAndDeleteRef(asyncId) {
RunOnMain {
do {
try p.resolve()
} catch let e {
log.warning("Unable to resolve cancel async promise: \(e)")
}
}
}
})
return p
}
internal mutating func updateHandleIfNeeded() throws {
if (_isCancelled) {
throw ContextError.ContextIsAlreadyCancelled
}
if dirty.contains(ContextFeatures.Deadline) {
try updateHandleForDeadline()
}
if dirty.contains(ContextFeatures.Cancellable) {
try updateHandleForCancellable()
}
}
private mutating func updateHandleForDeadline() throws {
guard let deadline = deadline else {
fatalError("Deadline was assumed to not be nil here, yet was marked as dirty")
}
let goHandle = try SwiftVError.catchAndThrowError { errPtr in
return swift_io_v_v23_context_VContext_nativeWithDeadline(
self.handle.goHandle, deadline.timeIntervalSince1970, errPtr)
}
_isCancellable = true
dirty.remove(ContextFeatures.Deadline)
handle = ContextHandle(goHandle)
}
private mutating func updateHandleForCancellable() throws {
let goHandle = try SwiftVError.catchAndThrowError { errPtr in
return swift_io_v_v23_context_VContext_nativeWithCancel(
self.handle.goHandle, errPtr)
}
_isCancellable = true
dirty.remove(ContextFeatures.Cancellable)
handle = ContextHandle(goHandle)
}
internal mutating func run<T>(@autoclosure block: () throws ->T) throws -> T {
guard !_isCancelled else { throw ContextError.ContextIsAlreadyCancelled }
try updateHandleIfNeeded()
return try block()
}
}
public class ContextHandle: CustomStringConvertible, CustomDebugStringConvertible {
internal let goHandle:GoContextHandle
internal init(_ goHandle:GoContextHandle) {
self.goHandle = goHandle
}
deinit {
if goHandle != 0 {
swift_io_v_v23_context_VContext_nativeFinalize(goHandle)
}
}
public var description: String { return "[ContextHandle \(goHandle)]" }
public var debugDescription: String { return "[ContextHandle handle=\(goHandle)]" }
}
struct ContextFeatures: OptionSetType {
let rawValue: Int
static let Deadline = ContextFeatures(rawValue: 1 << 1)
static let Cancellable = ContextFeatures(rawValue: 1 << 2)
// Make sure to add any new ones to updateHandleIfNeeded
// Unforutnately Swift doesn't yet give us a good enum-style way to iterate on this and prevent
// future bugs.
}
enum ContextError: ErrorType {
case ContextNotCancellable
case ContextIsAlreadyCancelled
}