Skip to content
This repository has been archived by the owner on Oct 15, 2023. It is now read-only.

Commit

Permalink
Merge pull request #6 from BennyDeBock/master
Browse files Browse the repository at this point in the history
Fix evenloop lifecycles issues and add case for normalclosure
  • Loading branch information
noahpistilli committed May 9, 2022
2 parents 48b9ea5 + 8d96616 commit ec8daf7
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 24 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
/docs/docsets
/Tests
.swift-version
.swiftpm
33 changes: 25 additions & 8 deletions Sources/Swiftcord/Gateway/Gateway.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,27 @@ protocol Gateway: AnyObject {
func start() async

func stop()

var eventLoopGroup: EventLoopGroup { get }

}

extension Gateway {

/// Starts the gateway connection
func start() async {
let loopgroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
let promise = loopgroup.next().makePromise(of: WebSocketErrorCode.self)
self.swiftcord.trace("Before creating loopgroup")
let promise = eventLoopGroup.next().makePromise(of: WebSocketErrorCode.self)

self.acksMissed = 0

self.swiftcord.trace("Before force unwrapping URL")
let url = URL(string: self.gatewayUrl)!
let path = self.gatewayUrl.components(separatedBy: "/?")[1]

let wsClient = WebSocketClient(eventLoopGroupProvider: .shared(loopgroup.next()), configuration: .init(tlsConfiguration: .clientDefault, maxFrameSize: 1 << 31))
self.swiftcord.trace("Create websocketclient")
let wsClient = WebSocketClient(eventLoopGroupProvider: .shared(eventLoopGroup.next()), configuration: .init(tlsConfiguration: .clientDefault, maxFrameSize: 1 << 31))

self.swiftcord.trace("Before connecting to the websocket")
try! await wsClient.connect(
scheme: url.scheme!,
host: url.host!,
Expand All @@ -74,37 +78,50 @@ extension Gateway {
self.isConnected = true

self.session?.onText { _, text in
self.swiftcord.trace("Handle incoming payload: \(text)")
await self.handlePayload(Payload(with: text))
}

self.session?.onClose.whenComplete { result in
self.swiftcord.trace("onclose.whencomplete")
switch result {
case .success():
self.isConnected = false

self.swiftcord.trace("Successfull onclose")
// If it is nil we just do nothing
if let closeCode = self.session?.closeCode {
promise.succeed(closeCode)
self.swiftcord.trace("promise succeeded with closeCode")
}
break
case .failure(_):
self.swiftcord.trace("session onclose failed")
break
}
}

print("[Swiftcord] Connected to Discord!")
self.swiftcord.log("[Swiftcord] Connected to Discord!")
}.get()

let errorCode = try! await promise.futureResult.get()

self.swiftcord.debug("Got errorCode successfully")

switch errorCode {
case .unknown(let int):
// Unknown will the codes sent by Discord
self.swiftcord.debug("Discord error code: \(errorCode). Trying to reconnect")
await self.handleDisconnect(for: Int(int))

case .goingAway:
self.swiftcord.debug("Websocket error code: \(errorCode). Trying to reconnect")
await self.handleDisconnect(for: 1001)
case .unexpectedServerError:
// Usually means the client lost their internet connection
self.swiftcord.debug("Websocket error code: \(errorCode). Trying to reconnect")
await self.handleDisconnect(for: 1011)
case .normalClosure:
// We always want to keep the bot alive so reconnect
self.swiftcord.debug("Websocket error code: \(errorCode). Trying to reconnect")
await self.handleDisconnect(for: 1000)
default:
self.swiftcord.error("Unknown Error Code: \(errorCode). Please restart the app.")
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Swiftcord/Gateway/Heartbeat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ extension Gateway {

guard self.acksMissed < 3 else {
Task {
print("[Swiftcord] Did not receive ACK from server, reconnecting...")
self.swiftcord.debug("[Swiftcord] Did not receive ACK from server, reconnecting...")
await self.reconnect()
}
return
Expand Down
34 changes: 29 additions & 5 deletions Sources/Swiftcord/Gateway/Shard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import Foundation
import Dispatch

import WebSocketKit
import NIOPosix
import NIOCore

/// WS class
class Shard: Gateway {
Expand Down Expand Up @@ -57,6 +59,9 @@ class Shard: Gateway {

/// Number of missed heartbeat ACKs
var acksMissed = 0

let eventLoopGroup: EventLoopGroup
let eventLoopGroupProvided: Bool

// MARK: Initializer
/**
Expand All @@ -65,11 +70,19 @@ class Shard: Gateway {
- parameter id: ID of the current shard
- parameter shardCount: Total number of shards bot needs to be connected to
*/
init(_ swiftcord: Swiftcord, _ id: Int, _ shardCount: Int, _ gatewayUrl: String) {
init(_ swiftcord: Swiftcord, _ id: Int, _ shardCount: Int, _ gatewayUrl: String, eventLoopGroup: EventLoopGroup?) {
self.swiftcord = swiftcord
self.id = id
self.shardCount = shardCount
self.gatewayUrl = gatewayUrl

if let eventLoopGroup = eventLoopGroup {
self.eventLoopGroupProvided = true
self.eventLoopGroup = eventLoopGroup
} else {
self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
self.eventLoopGroupProvided = false
}

self.heartbeatQueue = DispatchQueue(
label: "io.github.SketchMaster2001.Swiftcord.shard.\(id).heartbeat",
Expand All @@ -88,6 +101,12 @@ class Shard: Gateway {
interval: 60
)
}

deinit {
if !eventLoopGroupProvided {
try? eventLoopGroup.syncShutdownGracefully()
}
}

// MARK: Functions
/**
Expand Down Expand Up @@ -122,7 +141,8 @@ class Shard: Gateway {
self.isReconnecting = true

self.swiftcord.emit(.disconnect, with: self.id)

self.swiftcord.debug("status of the bot to disconnected")

guard let closeCode = CloseOP(rawValue: code) else {
self.swiftcord.log("Connection closed with unrecognized response \(code).")

Expand All @@ -142,7 +162,7 @@ class Shard: Gateway {

case .noInternet:
try! await Task.sleep(seconds: 10)
self.swiftcord.warn("Detected a loss of internet...")
self.swiftcord.debug("Detected a loss of internet...")
await self.reconnect()

case .shardingRequired:
Expand All @@ -162,10 +182,12 @@ class Shard: Gateway {
break

case .unexpectedServerError:
self.swiftcord.warn("Unexpected server error, check your internet connection. Reconnecting in 10 seconds")
self.swiftcord.debug("Unexpected server error, check your internet connection. Reconnecting in 10 seconds")
try! await Task.sleep(seconds: 10)
await self.reconnect()

case .goingAway:
self.swiftcord.debug("Going away: The server has moved or the browser is going away")
await self.reconnect()
default:
await self.reconnect()
}
Expand Down Expand Up @@ -255,8 +277,10 @@ class Shard: Gateway {

/// Used to reconnect to gateway
func reconnect() async {
self.swiftcord.warn("Status of isConnected: \(self.isConnected)")
if self.isConnected {
_ = try? await self.session?.close()
self.swiftcord.warn("Connection successfully closed")
}

self.isConnected = false
Expand Down
13 changes: 11 additions & 2 deletions Sources/Swiftcord/Gateway/ShardManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,24 @@
// Created by Alejandro Alonso
// Copyright © 2017 Alejandro Alonso. All rights reserved.
//

import NIOCore
class ShardManager {

/// The gateway url to connect to
var gatewayUrl: String?

/// Array of Shard class
var shards = [Shard]()

var eventLoopGroup: EventLoopGroup?

/// Parent Swiftcord class
weak var swiftcord: Swiftcord?

init(eventLoopGroup: EventLoopGroup?) {
self.eventLoopGroup = eventLoopGroup
}

/**
Used to create a set amount of shards
Expand All @@ -25,7 +33,7 @@ class ShardManager {
guard self.shards.isEmpty else { return }

for id in 0 ..< amount {
let shard = Shard(self.swiftcord!, id, amount, self.gatewayUrl!)
let shard = Shard(self.swiftcord!, id, amount, self.gatewayUrl!, eventLoopGroup: self.eventLoopGroup)
self.shards.append(shard)
Task {
await shard.start()
Expand Down Expand Up @@ -69,7 +77,8 @@ class ShardManager {
self.swiftcord!,
id,
self.swiftcord!.shardCount,
self.gatewayUrl!
self.gatewayUrl!,
eventLoopGroup: self.eventLoopGroup
)
self.shards.append(shard)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Swiftcord/Rest/MultipartBody.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func mimeType(for path: String) -> String {

let url = NSURL(string: path)!
let pathExtension = url.pathExtension

if let uti = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension,
pathExtension! as NSString, nil
Expand Down
23 changes: 16 additions & 7 deletions Sources/Swiftcord/Swiftcord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import FoundationNetworking
import Foundation
import Dispatch
import Logging
import NIOCore

/// Main Class for Swiftcord
open class Swiftcord: Eventable {
Expand Down Expand Up @@ -60,7 +61,7 @@ open class Swiftcord: Eventable {
/// Event listeners
public var listeners = [Event: [(Any) -> Void]]()

let logger = Logger(label: "io.github.SketchMaster2001.Swiftcord")
var logger = Logger(label: "io.github.SketchMaster2001.Swiftcord")

/// Optional options to apply to bot
var options: SwiftcordOptions
Expand All @@ -85,13 +86,15 @@ open class Swiftcord: Eventable {
public internal(set) var shardCount = 1

/// Shard Handler
lazy var shardManager = ShardManager()
lazy var shardManager = ShardManager(eventLoopGroup: self.eventLoopGroup)

/// How many shards are ready
var shardsReady = 0

/// The bot token
let token: String

let eventLoopGroup: EventLoopGroup?

/// Array of unavailable guilds the bot is currently connected to
public internal(set) var unavailableGuilds = [Snowflake: UnavailableGuild]()
Expand All @@ -116,9 +119,15 @@ open class Swiftcord: Eventable {
- parameter token: The bot token
- parameter options: Options to give bot (sharding, offline members, etc)
*/
public init(token: String, options: SwiftcordOptions = SwiftcordOptions()) {
public init(token: String, options: SwiftcordOptions = SwiftcordOptions(), logger: Logger? = nil, eventLoopGroup: EventLoopGroup?) {
self.options = options
self.token = token
if let logger = logger {
self.logger = logger
} else {
self.logger.logLevel = .info
}
self.eventLoopGroup = eventLoopGroup
}

// MARK: Functions
Expand Down Expand Up @@ -192,22 +201,22 @@ open class Swiftcord: Eventable {
let arguments = CommandLine.arguments

guard arguments.count > 1 else {
print("[Swiftcord] Insufficient argument count.")
self.logger.error("[Swiftcord] Insufficient argument count.")
return
}

guard arguments.contains("--shard") else {
print("[Swiftcord] Must specify shard with '--shard'")
self.logger.error("[Swiftcord] Must specify shard with '--shard'")
return
}

guard arguments.firstIndex(of: "--shard")! != arguments.count - 1 else {
print("[Swiftcord] '--shard' must not be the last argument. Correct syntax is '--shard {id here}'")
self.logger.error("[Swiftcord] '--shard' must not be the last argument. Correct syntax is '--shard {id here}'")
return
}

guard let shardId = Int(arguments[arguments.firstIndex(of: "--shard")! + 1]) else {
print("[Swiftcord] Shard ID could not be recognized.")
self.logger.error("[Swiftcord] Shard ID could not be recognized.")
return
}

Expand Down
1 change: 1 addition & 0 deletions Sources/Swiftcord/Utils/Enums.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ enum VoiceOP: Int {
enum CloseOP: Int {
case noInternet = 50,
clean = 1000,
goingAway = 1001,
unexpectedServerError = 1011,
unknownError = 4000,
unknownOP,
Expand Down
8 changes: 8 additions & 0 deletions Sources/Swiftcord/Utils/Log.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,13 @@ extension Swiftcord {
func error(_ message: Logger.Message) {
self.logger.error(message)
}

func debug(_ message: Logger.Message) {
self.logger.debug(message)
}

func trace(_ message: Logger.Message) {
self.logger.trace(message)
}

}

0 comments on commit ec8daf7

Please sign in to comment.