Skip to content

Commit

Permalink
Implementation of new library (lispkit thread future). New procedur…
Browse files Browse the repository at this point in the history
…e `wait-threads-terminated` in library `(lispkit thread)` to support waiting for all threads to be terminated.
  • Loading branch information
objecthub committed Jun 23, 2024
1 parent 1006cf5 commit 632fae1
Show file tree
Hide file tree
Showing 6 changed files with 296 additions and 17 deletions.
6 changes: 6 additions & 0 deletions LispKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,8 @@
CCD5B5EA2C15F14A002BF2F0 /* person in Copy JSON custom schema assets */ = {isa = PBXBuildFile; fileRef = CCD5B5E52C15EF9E002BF2F0 /* person */; };
CCD5B5ED2C16E6A6002BF2F0 /* JSON.scm in Copy examples */ = {isa = PBXBuildFile; fileRef = CCD5B5EB2C16E430002BF2F0 /* JSON.scm */; };
CCD5B5EE2C16E6B8002BF2F0 /* JSON.scm in Copy examples */ = {isa = PBXBuildFile; fileRef = CCD5B5EB2C16E430002BF2F0 /* JSON.scm */; };
CCD5B6082C276696002BF2F0 /* ThreadFutureLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD5B6072C276696002BF2F0 /* ThreadFutureLibrary.swift */; };
CCD5B6092C276696002BF2F0 /* ThreadFutureLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD5B6072C276696002BF2F0 /* ThreadFutureLibrary.swift */; };
CCD5E55C262E342600C45EA0 /* 189.sld in Copy pre-installed SRFI libraries */ = {isa = PBXBuildFile; fileRef = CCD5E558262E309500C45EA0 /* 189.sld */; };
CCD5E55D262E342600C45EA0 /* 214.sld in Copy pre-installed SRFI libraries */ = {isa = PBXBuildFile; fileRef = CCD5E559262E31AE00C45EA0 /* 214.sld */; };
CCD5E561262E343E00C45EA0 /* 189.sld in Copy pre-installed SRFI libraries */ = {isa = PBXBuildFile; fileRef = CCD5E558262E309500C45EA0 /* 189.sld */; };
Expand Down Expand Up @@ -3609,6 +3611,7 @@
CCD5B5DC2C150189002BF2F0 /* LispKit-JSON-Schema.scm */ = {isa = PBXFileReference; lastKnownFileType = text; path = "LispKit-JSON-Schema.scm"; sourceTree = "<group>"; };
CCD5B5E52C15EF9E002BF2F0 /* person */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = person; path = Sources/LispKit/Resources/Assets/JSON/Schema/Custom/person; sourceTree = SOURCE_ROOT; };
CCD5B5EB2C16E430002BF2F0 /* JSON.scm */ = {isa = PBXFileReference; lastKnownFileType = text; path = JSON.scm; sourceTree = "<group>"; };
CCD5B6072C276696002BF2F0 /* ThreadFutureLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadFutureLibrary.swift; sourceTree = "<group>"; };
CCD5E558262E309500C45EA0 /* 189.sld */ = {isa = PBXFileReference; lastKnownFileType = text; path = 189.sld; sourceTree = "<group>"; };
CCD5E559262E31AE00C45EA0 /* 214.sld */ = {isa = PBXFileReference; lastKnownFileType = text; path = 214.sld; sourceTree = "<group>"; };
CCD5E55A262E32EE00C45EA0 /* SRFI-214.scm */ = {isa = PBXFileReference; lastKnownFileType = text; path = "SRFI-214.scm"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4676,6 +4679,7 @@
CC2FB0792AA3D7A800A20C71 /* TarArchiveLibrary.swift */,
CC517E5729A217980096FC72 /* CryptoLibrary.swift */,
CC2DA7462773195600DE69CF /* ThreadLibrary.swift */,
CCD5B6072C276696002BF2F0 /* ThreadFutureLibrary.swift */,
CC1D533C2BF1709E00EE1A24 /* JSONLibrary.swift */,
CCD5B5202C0D231E002BF2F0 /* JSONSchemaLibrary.swift */,
CC38528A23126F7A001D5E56 /* InternalLibrary.swift */,
Expand Down Expand Up @@ -5437,6 +5441,7 @@
CCB585BB1E15AA4D0064BB18 /* Heap.swift in Sources */,
CC93A6611D0777A9008350C8 /* TextOutput.swift in Sources */,
CC5BBC791D5268D000367E8F /* HashTable.swift in Sources */,
CCD5B6082C276696002BF2F0 /* ThreadFutureLibrary.swift in Sources */,
CC2DA7572781037B00DE69CF /* EvalCondition.swift in Sources */,
CCCB66E4283056E30004827D /* StyledTextLibrary.swift in Sources */,
CC6A3B6A1C539DCA00E962E2 /* ManagedObjectPool.swift in Sources */,
Expand Down Expand Up @@ -5491,6 +5496,7 @@
CC1D53332BE7AB4000EE1A24 /* CustomExpr.swift in Sources */,
CCC48CC625E5B98600D082AD /* BytevectorLibrary.swift in Sources */,
CCC48C6F25E5B94300D082AD /* Timer.swift in Sources */,
CCD5B6092C276696002BF2F0 /* ThreadFutureLibrary.swift in Sources */,
CCC48CC825E5B98600D082AD /* RegexpLibrary.swift in Sources */,
CCC48CAE25E5B97100D082AD /* SourceManager.swift in Sources */,
CCC48CD825E5B98600D082AD /* PortLibrary.swift in Sources */,
Expand Down
3 changes: 3 additions & 0 deletions Sources/LispKit/Compiler/EvalError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ public enum EvalError: Int, Hashable {
case unableToCreateJSONPatch
case unsupportedJSONSchemaDialect
case undefinedDefaultSchemaRegistry
case settingFutureValueTwice

public var message: String {
switch self {
Expand Down Expand Up @@ -478,6 +479,8 @@ public enum EvalError: Int, Hashable {
return "unsupported JSON schema dialect: $0"
case .undefinedDefaultSchemaRegistry:
return "undefined default JSON schema registry"
case .settingFutureValueTwice:
return "trying to set value of future $0 twice; attempted to assign $1"
}
}

Expand Down
25 changes: 8 additions & 17 deletions Sources/LispKit/Primitives/JSONSchemaLibrary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,30 +51,21 @@ public final class JSONSchemaLibrary: NativeLibrary {
try super.init(in: context)
}

/// Name of the library.
/// Name of the library.
public override class var name: [String] {
return ["lispkit", "json", "schema"]
}

/// Dependencies of the library.
/// Dependencies of the library.
public override func dependencies() {
/*
self.`import`(from: ["lispkit", "core"], "define", "define-syntax", "syntax-rules", "lambda",
"apply", "values")
self.`import`(from: ["lispkit", "control"], "begin", "let")
self.`import`(from: ["lispkit", "dynamic"], "dynamic-wind")
self.`import`(from: ["lispkit", "system"], "current-second")
self.`import`(from: ["lispkit", "math"], "+", "-")
self.`import`(from: ["lispkit", "list"], "cons", "map")
*/
}

/// Declarations of the library.
}

/// Declarations of the library.
public override func declarations() {
// Type tags
self.define("json-schema-registry-tag", as: JSONSchemaRegistry.type.objectTypeTag())
self.define("json-schema-tag", as: JSONSchemaResource.type.objectTypeTag())
self.define("validation-result-tag", as: JSONValidationResult.type.objectTypeTag())
self.define("schema-registry-type-tag", as: JSONSchemaRegistry.type.objectTypeTag())
self.define("json-schema-type-tag", as: JSONSchemaResource.type.objectTypeTag())
self.define("validation-result-type-tag", as: JSONValidationResult.type.objectTypeTag())

// Parameter objects
self.define("current-schema-registry", as: self.registryParam)
Expand Down
2 changes: 2 additions & 0 deletions Sources/LispKit/Primitives/LibraryRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public struct LibraryRegistry {
TarArchiveLibrary.self,
CryptoLibrary.self,
ThreadLibrary.self,
ThreadFutureLibrary.self,
JSONLibrary.self,
JSONSchemaLibrary.self
]
Expand Down Expand Up @@ -99,6 +100,7 @@ public struct LibraryRegistry {
TarArchiveLibrary.self,
CryptoLibrary.self,
ThreadLibrary.self,
ThreadFutureLibrary.self,
JSONLibrary.self,
JSONSchemaLibrary.self
]
Expand Down
270 changes: 270 additions & 0 deletions Sources/LispKit/Primitives/ThreadFutureLibrary.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
//
// ThreadFutureLibrary.swift
// LispKit
//
// Created by Matthias Zenger on 22/06/2024.
// Copyright © 2024 ObjectHub. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

public final class ThreadFutureLibrary: NativeLibrary {

/// Initialize symbols
public required init(in context: Context) throws {
try super.init(in: context)
}

/// Name of the library.
public override class var name: [String] {
return ["lispkit", "thread", "future"]
}

/// Dependencies of the library.
public override func dependencies() {
self.`import`(from: ["lispkit", "core"], "define", "define-syntax", "syntax-rules", "lambda",
"case-lambda")
self.`import`(from: ["lispkit", "control"], "let", "if")
self.`import`(from: ["lispkit", "dynamic"], "try", "raise", "error")
self.`import`(from: ["lispkit", "list"], "cdr", "car")
self.`import`(from: ["lispkit", "thread"], "spawn")
}

/// Declarations of the library.
public override func declarations() {
self.define("future-type-tag", as: Future.type.objectTypeTag())
self.define(Procedure("future?", self.isFuture))
self.define(Procedure("_make-future", self.makeFuture))
self.define(Procedure("_future-get", self.futureGet))
self.define(Procedure("_future-set!", self.futureSet))
self.define(Procedure("future-done?", self.futureDone))
self.define("make-future", via: """
(define (make-future thunk)
(let ((future (_make-future)))
(spawn (lambda ()
(try (lambda () (_future-set! future (thunk) #f))
(lambda (e) (_future-set! future e #t)))))
future))
""")
self.define("make-evaluated-future", via: """
(define (make-evaluated-future x)
(let ((future (_make-future)))
(_future-set! future x #f)
future))
""")
self.define("make-failing-future", via: """
(define (make-failing-future x)
(let ((future (_make-future)))
(_future-set! future x #t)
future))
""")
self.define("future", via: """
(define-syntax future
(syntax-rules ()
((_ expr ...)
(make-future (lambda () expr ...)))))
""")
self.define("future-get", via: """
(define future-get
(case-lambda
((future)
(let ((result (_future-get future)))
(if (cdr result) (raise (car result)) (car result))))
((future timeout)
(let ((result (_future-get future timeout)))
(if result
(if (cdr result) (raise (car result)) (car result))
(error "future-get timed out for $0" future))))
((future timeout default)
(let ((result (_future-get future timeout)))
(if result
(if (cdr result) (raise (car result)) (car result))
default)))
)
)
""")
self.define("touch", via: """
(define (touch future)
(let ((result (_future-get future)))
(if (cdr result) (raise (car result)) (car result))))
""")
}

private func future(from expr: Expr) throws -> Future {
guard case .object(let obj) = expr, let future = obj as? Future else {
throw RuntimeError.type(expr, expected: [Future.type])
}
return future
}

private func isFuture(expr: Expr) -> Expr {
guard case .object(let obj) = expr, obj is Future else {
return .false
}
return .true
}

private func makeFuture() throws -> Expr {
return .object(Future())
}

private func futureDone(expr: Expr) throws -> Expr {
return .makeBoolean(try self.future(from: expr).resultAvailable(in: self.context))
}

private func futureGet(expr: Expr, timeout: Expr?) throws -> Expr {
let timeout = try timeout?.asDouble(coerce: true)
if let (expr, error) =
try self.future(from: expr).getResult(in: self.context, timeout: timeout) {
return .pair(expr, .makeBoolean(error))
} else {
return .false
}
}

private func futureSet(expr: Expr, value: Expr, error: Expr) throws -> Expr {
guard try self.future(from: expr).setResult(in: self.context,
to: value,
raise: error.isTrue) else {
throw RuntimeError.eval(.settingFutureValueTwice, expr, value)
}
return .void
}
}

public final class Future: NativeObject {

/// Type representing zip archives
public static let type = Type.objectType(Symbol(uninterned: "future"))

/// Mutex to protect the result
public let mutex: EvalMutex

/// Condition variable to manage threads blocking on retrieving a result
public let condition: EvalCondition

/// The result once computed
public var result: (value: Expr, error: Bool)? = nil

/// Initializer
public override init() {
self.mutex = EvalMutex()
self.condition = EvalCondition()
super.init()
}

private func lock(in context: Context) throws -> Bool {
guard let current = context.evaluator.threads.current else {
throw RuntimeError.eval(.mutexUseInInvalidContext, .object(self.mutex))
}
return try mutex.lock(in: current.value, for: current.value)
}

private func unlock(in context: Context) throws {
guard let current = context.evaluator.threads.current else {
throw RuntimeError.eval(.mutexUseInInvalidContext, .object(self.mutex))
}
_ = try mutex.unlock(in: current.value)
}

private func wait(in context: Context, timeout: TimeInterval? = nil) throws {
guard let current = context.evaluator.threads.current else {
throw RuntimeError.eval(.mutexUseInInvalidContext, .object(self.mutex))
}
_ = try mutex.unlock(in: current.value, condition: self.condition, timeout: timeout)
}

public func resultAvailable(in context: Context) throws -> Bool {
if try self.lock(in: context) {
defer {
try? self.unlock(in: context)
}
return self.result != nil
} else {
throw RuntimeError.eval(.mutexUseInInvalidContext, .object(self.mutex))
}
}

public func setResult(in context: Context, to result: Expr, raise: Bool = false) throws -> Bool {
if try self.lock(in: context) {
defer {
try? self.unlock(in: context)
}
guard self.result == nil else {
return false
}
self.result = (result, raise)
self.condition.signal()
return true
} else {
throw RuntimeError.eval(.mutexUseInInvalidContext, .object(self.mutex))
}
}

public func getResult(in context: Context, timeout: TimeInterval? = nil) throws -> (value: Expr, error: Bool)? {
if try self.lock(in: context) {
if self.result == nil {
try self.wait(in: context, timeout: timeout)
}
guard let result = self.result else {
return nil
}
defer {
try? self.unlock(in: context)
}
return result
} else {
throw RuntimeError.eval(.mutexUseInInvalidContext, .object(self.mutex))
}
}

public override var type: Type {
return Self.type
}

public override var string: String {
return "#<\(self.tagString)>"
}

public override var tagString: String {
switch self.result {
case .none:
return "\(Self.type) \(self.identityString) ?"
case .some((value: let expr, error: false)):
return "\(Self.type) \(self.identityString) success: \(expr)"
case .some((value: let expr, error: true)):
return "\(Self.type) \(self.identityString) error: \(expr)"
}
}

public override func mark(in gc: GarbageCollector) {
gc.markLater(.object(self.mutex))
gc.markLater(.object(self.condition))
if let expr = self.result?.value {
gc.markLater(expr)
}
}

public override func unpack(in context: Context) -> Exprs {
switch self.result {
case .none:
return [.makeString(identityString)]
case .some((value: let expr, error: false)):
return [.makeString(identityString), expr, .false]
case .some((value: let expr, error: true)):
return [.makeString(identityString), expr, .true]
}
}
}
Loading

0 comments on commit 632fae1

Please sign in to comment.