Skip to content

Commit

Permalink
Fastly Cache (#21)
Browse files Browse the repository at this point in the history
* Start cache api

* Cache policy

* Cleanup names

* Fix return policy

* Use short syntax

* Cancel trx on error

* Move to public Cache

* Fix usable

* Use empty options set

* Deps

* Add stubs

* Add cache state

* Fix initial age

* Try closing writer

* Optional length
  • Loading branch information
AndrewBarba authored Sep 28, 2023
1 parent dcc7014 commit 29fb196
Show file tree
Hide file tree
Showing 8 changed files with 475 additions and 69 deletions.
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-crypto",
"state" : {
"revision" : "33a20e650c33f6d72d822d558333f2085effa3dc",
"version" : "2.5.0"
"revision" : "60f13f60c4d093691934dc6cfdf5f508ada1f894",
"version" : "2.6.0"
}
}
],
Expand Down
84 changes: 84 additions & 0 deletions Sources/Compute/Cache.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// Cache.swift
//
//
// Created by Andrew Barba on 9/13/23.
//

public struct Cache: Sendable {

public static func getOrSet(_ key: String, _ handler: () async throws -> (FetchResponse, CachePolicy)) async throws -> Entry {
let trx = try await Fastly.Cache.getOrSet(key) {
let (res, cachePolicy) = try await handler()
let length = res.headers[.contentLength].flatMap(Int.init)
return await (.body(res.body.body, length: length), cachePolicy)
}
return try .init(trx)
}

public static func getOrSet(_ key: String, _ handler: () async throws -> ([UInt8], CachePolicy)) async throws -> Entry {
let trx = try await Fastly.Cache.getOrSet(key) {
let (bytes, cachePolicy) = try await handler()
return (.bytes(bytes), cachePolicy)
}
return try .init(trx)
}

public static func getOrSet(_ key: String, _ handler: () async throws -> (String, CachePolicy)) async throws -> Entry {
let trx = try await Fastly.Cache.getOrSet(key) {
let (text, cachePolicy) = try await handler()
return (.bytes(.init(text.utf8)), cachePolicy)
}
return try .init(trx)
}

public static func getOrSet(_ key: String, _ handler: () async throws -> (Data, CachePolicy)) async throws -> Entry {
let trx = try await Fastly.Cache.getOrSet(key) {
let (data, cachePolicy) = try await handler()
return (.bytes(data.bytes), cachePolicy)
}
return try .init(trx)
}

public static func getOrSet(_ key: String, _ handler: () async throws -> ([String: Sendable], CachePolicy)) async throws -> Entry {
let trx = try await Fastly.Cache.getOrSet(key) {
let (json, cachePolicy) = try await handler()
let data = try JSONSerialization.data(withJSONObject: json)
return (.bytes(data.bytes), cachePolicy)
}
return try .init(trx)
}

public static func getOrSet(_ key: String, _ handler: () async throws -> ([Sendable], CachePolicy)) async throws -> Entry {
let trx = try await Fastly.Cache.getOrSet(key) {
let (json, cachePolicy) = try await handler()
let data = try JSONSerialization.data(withJSONObject: json)
return (.bytes(data.bytes), cachePolicy)
}
return try .init(trx)
}
}

extension Cache {

public struct Entry: Sendable {

public let body: ReadableBody

public let age: Int

public let hits: Int

public let contentLength: Int

public let state: CacheState

internal init(_ trx: Fastly.Cache.Transaction) throws {
self.body = try ReadableWasiBody(trx.getBody())
self.age = try trx.getAge()
self.hits = try trx.getHits()
self.contentLength = try trx.getLength()
self.state = try trx.getState()
}
}
}
4 changes: 4 additions & 0 deletions Sources/Compute/Fastly/FastlyBody.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ extension Fastly {
try wasi(fastly_http_body__close(handle))
}

public mutating func abandon() throws {
try wasi(fastly_http_body__abandon(handle))
}

@discardableResult
public mutating func write(_ bytes: [UInt8], location: BodyWriteEnd = .back) throws -> Int {
defer { used = true }
Expand Down
144 changes: 144 additions & 0 deletions Sources/Compute/Fastly/FastlyCache.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//
// FastlyCache.swift
//
//
// Created by Andrew Barba on 9/13/23.
//

import ComputeRuntime

extension Fastly {
public struct Cache: Sendable {

public typealias InsertResult = (data: HandlerData, cachePolicy: CachePolicy)

public static func getOrSet(_ key: String, _ handler: () async throws -> InsertResult) async throws -> Transaction {
// Open the transaction
let trx = try Transaction.lookup(key)

// Get current transaction state
let state = try trx.getState()

// If state is usable then return the body from cache
guard state.contains(.mustInsertOrUpdate) else {
return trx
}

do {
// If its not usable then begin executing handler with new value
let (data, cachePolicy) = try await handler()

// Get an instance to the insert handle
var writer = try trx.insertAndStreamBack(cachePolicy: cachePolicy, length: data.length)

// Append bytes from handler to writeable body
switch data {
case .body(let body, _):
try writer.body.append(body)
case .bytes(let bytes):
try writer.body.write(bytes)
}

// Finish the body
try writer.body.close()

return writer.transaction
} catch {
// Cancel the transaction if something went wrong
try trx.cancel()

// Rethrow original error
throw error
}
}
}
}

extension Fastly.Cache {
public enum HandlerData {
case body(_ body: Fastly.Body, length: Int? = nil)
case bytes(_ bytes: [UInt8])

public var length: Int? {
switch self {
case .body(_, let length):
return length
case .bytes(let bytes):
return bytes.count
}
}
}
}

extension Fastly.Cache {
public struct Transaction: Sendable {

internal let handle: WasiHandle

internal init(_ handle: WasiHandle) {
self.handle = handle
}

public static func lookup(_ key: String) throws -> Transaction {
var handle: WasiHandle = 0
let options = CacheLookupOptions.none
var config = CacheLookupConfig()
try wasi(fastly_cache__cache_transaction_lookup(key, key.utf8.count, options.rawValue, &config, &handle))
return Transaction(handle)
}

public func insertAndStreamBack(cachePolicy: CachePolicy, length: Int?) throws -> (body: Fastly.Body, transaction: Transaction) {
var bodyHandle: WasiHandle = 0
var cacheHandle: WasiHandle = 0
var options: CacheWriteOptions = []
var config = CacheWriteConfig()
config.max_age_ns = .init(cachePolicy.maxAge) * 1_000_000_000
if cachePolicy.staleMaxAge > 0 {
options.insert(.staleWhileRevalidateNs)
config.stale_while_revalidate_ns = .init(cachePolicy.staleMaxAge) * 1_000_000_000
}
if let length {
options.insert(.length)
config.length = .init(length)
}
try wasi(fastly_cache__cache_transaction_insert_and_stream_back(handle, options.rawValue, &config, &bodyHandle, &cacheHandle))
return (.init(bodyHandle), .init(cacheHandle))
}

public func getState() throws -> CacheState {
var value: UInt8 = 0
try wasi(fastly_cache__cache_get_state(handle, &value))
return CacheState(rawValue: value)
}

public func getAge() throws -> Int {
var value: UInt64 = 0
try wasi(fastly_cache__cache_get_age_ns(handle, &value))
return .init(value / 1_000_000_000)
}

public func getLength() throws -> Int {
var value: UInt64 = 0
try wasi(fastly_cache__cache_get_length(handle, &value))
return .init(value)
}

public func getHits() throws -> Int {
var value: UInt64 = 0
try wasi(fastly_cache__cache_get_hits(handle, &value))
return .init(value)
}

public func getBody() throws -> Fastly.Body {
var bodyHandle: WasiHandle = 0
let options = CacheGetBodyOptions.none
var config = CacheGetBodyConfig()
try wasi(fastly_cache__cache_get_body(handle, options.rawValue, &config, &bodyHandle))
return .init(bodyHandle)
}

public func cancel() throws {
try wasi(fastly_cache__cache_transaction_cancel(handle))
}
}
}
64 changes: 48 additions & 16 deletions Sources/Compute/Fastly/FastlyStubs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,7 @@
/// avoid link failure.
/// When running with Compute runtime library, they are ignored completely.
#if !arch(wasm32)

/* TYPES */

struct DynamicBackendConfig: Sendable {
var host_override: UnsafePointer<CChar>! = nil
var host_override_len: Int = 0
var connect_timeout_ms: Int = 0
var first_byte_timeout_ms: Int = 0
var between_bytes_timeout_ms: Int = 0
var ssl_min_version: Int = 0
var ssl_max_version: Int = 0
var cert_hostname: UnsafePointer<CChar>! = nil
var cert_hostname_len: Int = 0
var sni_hostname: UnsafePointer<CChar>! = nil
var sni_hostname_len: Int = 0
}
import ComputeRuntime

/* FASTLY_ABI */

Expand Down Expand Up @@ -72,6 +57,8 @@ func fastly_http_body__append(_ dest: WasiHandle, _ src: WasiHandle) -> Int32 {

func fastly_http_body__close(_ handle: WasiHandle) -> Int32 { fatalError() }

func fastly_http_body__abandon(_ handle: WasiHandle) -> Int32 { fatalError() }

func fastly_http_body__write(_ handle: WasiHandle, _ data: UnsafePointer<UInt8>!, _ data_len: Int, _ body_end: Int32, _ nwritten: UnsafeMutablePointer<Int>!) -> Int32 { fatalError() }

func fastly_http_body__read(_ handle: WasiHandle, _ data: UnsafeMutablePointer<UInt8>!, _ data_max_len: Int, _ nwritten: UnsafeMutablePointer<Int>!) -> Int32 { fatalError() }
Expand Down Expand Up @@ -170,4 +157,49 @@ func fastly_http_resp__framing_headers_mode_set(_ resp_handle: WasiHandle, _ mod

func fastly_http_resp__http_keepalive_mode_set(_ resp_handle: WasiHandle, _ mode: UInt32) -> Int32 { fatalError() }

func fastly_cache__cache_transaction_lookup(
_ cache_key: UnsafePointer<CChar>!,
_ cache_key_len: Int,
_ options_mask: UInt32,
_ config: UnsafeMutablePointer<CacheLookupConfig>!,
_ ret: UnsafeMutablePointer<WasiHandle>!
) -> Int32 { fatalError() }

func fastly_cache__cache_transaction_insert_and_stream_back(
_ handle: WasiHandle,
_ options_mask: UInt32,
_ config: UnsafeMutablePointer<CacheWriteConfig>!,
_ ret_body: UnsafeMutablePointer<WasiHandle>!,
_ ret_cache: UnsafeMutablePointer<WasiHandle>!
) -> Int32 { fatalError() }

func fastly_cache__cache_get_state(
_ handle: WasiHandle,
_ ret: UnsafeMutablePointer<UInt8>!
) -> Int32 { fatalError() }

func fastly_cache__cache_get_age_ns(
_ handle: WasiHandle,
_ ret: UnsafeMutablePointer<UInt64>!
) -> Int32 { fatalError() }

func fastly_cache__cache_get_length(
_ handle: WasiHandle,
_ ret: UnsafeMutablePointer<UInt64>!
) -> Int32 { fatalError() }

func fastly_cache__cache_get_hits(
_ handle: WasiHandle,
_ ret: UnsafeMutablePointer<UInt64>!
) -> Int32{ fatalError() }

func fastly_cache__cache_get_body(
_ handle: WasiHandle,
_ options_mask: UInt32,
_ config: UnsafeMutablePointer<CacheGetBodyConfig>!,
_ ret: UnsafeMutablePointer<WasiHandle>!
) -> Int32 { fatalError() }

func fastly_cache__cache_transaction_cancel(_ handle: WasiHandle) -> Int32 { fatalError() }

#endif
Loading

0 comments on commit 29fb196

Please sign in to comment.