Skip to content

Commit

Permalink
added threadlocal
Browse files Browse the repository at this point in the history
  • Loading branch information
ypopovych committed Jul 7, 2023
1 parent 88b7819 commit 369b321
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 18 deletions.
39 changes: 21 additions & 18 deletions Sources/ConfigurationCodable/Decodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import Foundation
import CoreFoundation

public typealias CodableWithConfiguration =
DecodableWithConfiguration & EncodableWithConfiguration
Expand All @@ -21,37 +22,37 @@ public struct DecodableWrapper<Wrapped: DecodableWithConfiguration>: Decodable {

@inlinable
public init(from decoder: Decoder) throws {
guard let configuration = Self.configuration else {
guard let anyconf = Self.configuration() else {
throw DecodingError.valueNotFound(
Wrapped.DecodingConfiguration.self,
DecodingError.Context(codingPath: decoder.codingPath,
debugDescription: "Configuration is empty")
)
}
value = try Wrapped(from: decoder, configuration: configuration)
guard let conf = anyconf as? Wrapped.DecodingConfiguration else {
let error = "Configuration has different type \(type(of: anyconf))"
throw DecodingError.typeMismatch(
Wrapped.DecodingConfiguration.self,
DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: error
)
)
}
value = try Wrapped(from: decoder, configuration: conf)
}

@inlinable
public static func set(configuration: Wrapped.DecodingConfiguration) {
Thread.current.threadDictionary[threadLocalKey] = configuration
DecodableThreadLocalKey.set(value: configuration)
}

@inlinable
public static var configuration: Wrapped.DecodingConfiguration? {
defer { clear() }
guard let conf = Thread.current.threadDictionary[threadLocalKey] else {
return nil
}
return conf as? Wrapped.DecodingConfiguration
// One time call. Will remove configuration from ThreadLocal
// Any because body is unknown (try cast it)
public static func configuration() -> Any? {
DecodableThreadLocalKey.replace(with: nil)
}

@inlinable
public static func clear() {
Thread.current.threadDictionary.removeObject(forKey: threadLocalKey)
}

@inlinable
public static var threadLocalKey: String { "__DecodableConfigurationThreadLocalKey__" }
public static var threadLocalKey: ThreadLocal<Any> { DecodableThreadLocalKey }
}

public extension JSONDecoder {
Expand Down Expand Up @@ -91,3 +92,5 @@ public extension KeyedDecodingContainer {
return try decodeIfPresent(DecodableWrapper<T>.self, forKey: key)?.value
}
}

private let DecodableThreadLocalKey = ThreadLocal<Any>()
70 changes: 70 additions & 0 deletions Sources/ConfigurationCodable/ThreadLocal.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// ThreadLocal.swift
//
//
// Created by Yehor Popovych on 07/07/2023.
//

import Foundation
#if canImport(Glibc)
import Glibc
#endif

public final class Box<V> {
public let value: V

public init(_ value: V) {
self.value = value
}

public func retained() -> UnsafeMutableRawPointer {
Unmanaged.passRetained(self).toOpaque()
}

public func unretained() -> UnsafeMutableRawPointer {
Unmanaged.passUnretained(self).toOpaque()
}

public static func retained(ptr: UnsafeRawPointer) -> Self {
Unmanaged<Self>.fromOpaque(ptr).takeRetainedValue()
}

public static func unretained(ptr: UnsafeRawPointer) -> Self {
Unmanaged<Self>.fromOpaque(ptr).takeUnretainedValue()
}
}

public final class ThreadLocal<V> {
var key: pthread_key_t

public init() {
key = pthread_key_t()
let result = pthread_key_create(&key) { ptr in
guard let ptr = (ptr as UnsafeMutableRawPointer?) else {
return
}
let _ = Box<Any>.retained(ptr: ptr)
}
precondition(result == 0, "pthread_key_create failed")
}

public func get() -> V? {
pthread_getspecific(key).map { Box<V>.unretained(ptr: $0).value }
}

public func replace(with value: V?) -> V? {
let ptr = pthread_getspecific(key)
let result = pthread_setspecific(key, value.map { Box($0).retained() })
precondition(result == 0, "pthread_setspecific failed")
return ptr.map { Box<V>.retained(ptr: $0).value }
}

@inlinable public func set(value: V?) { let _ = replace(with: value) }

@inlinable public func remove() { let _ = replace(with: nil) }

deinit {
let result = pthread_key_delete(key)
precondition(result == 0, "pthread_key_delete failed")
}
}

0 comments on commit 369b321

Please sign in to comment.