Skip to content

Commit

Permalink
Shutdown HTTPClient in deinit of Client (#31)
Browse files Browse the repository at this point in the history
* Shutdown HTTPClient in deinit of Client

* Add guard, update README

* Convert httpClient param to optional, implement referencing logic

* Update README
  • Loading branch information
dylanshine authored Apr 4, 2023
1 parent d7cc248 commit af8d270
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 6 deletions.
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ OPENAI_ORGANIZATION="YOUR-ORGANIZATION"
~~~~
⚠️ OpenAI strongly recommends developers of client-side applications proxy requests through a separate backend service to keep their API key safe. API keys can access and manipulate customer billing, usage, and organizational data, so it's a significant risk to [expose](https://nshipster.com/secrets/) them.

Create a `OpenAIKit.Client` using a httpClient and configuration.
Create a `OpenAIKit.Client` by passing a configuration.

~~~~swift

Expand All @@ -42,12 +42,21 @@ var organization: String {

...

let configuration = Configuration(apiKey: apiKey, organization: organization)

let openAIClient = OpenAIKit.Client(configuration: configuration)
~~~~

By default, a `HTTPClient` will be internally managed by the framework. For more granular control, you may also instantiate the `OpenAIKit.Client` by passing in your own `HTTPClient`.

~~~~swift
...

let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
defer {
// it's important to shutdown the httpClient after all requests are done, even if one failed. See: https://github.com/swift-server/async-http-client
try? httpClient.syncShutdown()
}
let configuration = Configuration(apiKey: apiKey, organization: organization)

let openAIClient = OpenAIKit.Client(httpClient: httpClient, configuration: configuration)
~~~~
Expand Down
25 changes: 21 additions & 4 deletions Sources/OpenAIKit/Client/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import NIO
import NIOHTTP1
import Foundation

public struct Client {
public final class Client {

public let audio: AudioProvider
public let chats: ChatProvider
Expand All @@ -14,14 +14,21 @@ public struct Client {
public let images: ImageProvider
public let models: ModelProvider
public let moderations: ModerationProvider

// Hold onto reference of internally created HTTPClient to perform appropriate shutdowns.
private let _httpClient: HTTPClient?

public init(
httpClient: HTTPClient,
// If an HTTPClient is not provided, an internal one will be created, and will be shutdown after the lifecycle of the Client
httpClient: HTTPClient? = nil,
configuration: Configuration
) {


// If an httpClient is provided, don't hold reference to it.
self._httpClient = httpClient == nil ? HTTPClient(eventLoopGroupProvider: .createNew) : nil

let requestHandler = RequestHandler(
httpClient: httpClient,
httpClient: httpClient ?? _httpClient!,
configuration: configuration
)

Expand All @@ -36,5 +43,15 @@ public struct Client {
self.moderations = ModerationProvider(requestHandler: requestHandler)

}

deinit {
/**
syncShutdown() must not be called when on an EventLoop.
Calling syncShutdown() on any EventLoop can lead to deadlocks.
*/
guard MultiThreadedEventLoopGroup.currentEventLoop == nil else { return }

try? _httpClient?.syncShutdown()
}

}
11 changes: 11 additions & 0 deletions Tests/OpenAIKitTests/OpenAIKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ final class OpenAIKitTests: XCTestCase {

}

func test_usingInternalHTTPClient() async throws {

let client = Client(
configuration: .init(apiKey: "YOUR-API-KEY")
)

let models = try await client.models.list()
print(models)

}

func test_listModels() async throws {
let models = try await client.models.list()
print(models)
Expand Down

0 comments on commit af8d270

Please sign in to comment.