0

在我的基于 Swift 语言的服务器的临时设计中,它由两个主要部分组成:

  1. 一个请求处理部分,它只处理服务器 API 的 HTTP 请求。对于服务器来说,这只是常规操作。

  2. 一个基于计时器的线程,它定期唤醒和处理某些类型的请求创建的任务。这些任务位于服务器的两个部分都可以访问的数据库表中。

服务器的这两个部分位于同一个 Docker 容器中,并在 AWS Elastic Beanstalk 上运行。我关心的是第二个基于计时器的部分。

它使用以下计时器:

// Adapted from https://gist.github.com/danielgalasko/1da90276f23ea24cb3467c33d2c05768

import Foundation
import LoggerAPI

/// RepeatingTimer mimics the API of DispatchSourceTimer but in a way that prevents
/// crashes that occur from calling resume multiple times on a timer that is
/// already resumed (noted by https://github.com/SiftScience/sift-ios/issues/52
class RepeatingTimer {

    let timeInterval: TimeInterval
    
    init(timeInterval: TimeInterval) {
        self.timeInterval = timeInterval
    }
    
    private lazy var timer: DispatchSourceTimer = {
        let t = DispatchSource.makeTimerSource()
        t.schedule(deadline: .now() + .seconds(Int(self.timeInterval)), repeating: .seconds(Int(self.timeInterval)))
        t.setEventHandler(handler: { [weak self] in
            self?.eventHandler?()
        })
        return t
    }()

    var eventHandler: (() -> Void)?

    private enum State {
        case suspended
        case resumed
    }

    private var state: State = .suspended

    deinit {
        Log.debug("RepeatingTimer: deinit")
        eventHandler = nil

        timer.setEventHandler {}
        timer.cancel()
        /*
         If the timer is suspended, calling cancel without resuming
         triggers a crash. This is documented here https://forums.developer.apple.com/thread/15902
         */
        resume()
    }

    func resume() {
        if state == .resumed {
            return
        }
        state = .resumed
        timer.resume()
    }

    func suspend() {
        if state == .suspended {
            return
        }
        state = .suspended
        timer.suspend()
    }
}

在服务器启动时调用它,如下所示:

        repeatingTimer = RepeatingTimer(timeInterval: interval)
        repeatingTimer?.eventHandler = { [weak self] in
            guard let self = self else { return }
            
            Log.debug("PeriodicUploader: About to run Uploader")

            self.uploader = Uploader(services: self.services, delegate: nil)

            do {
                try self.uploader.run()
            } catch let error {
                Log.error("\(error)")
            }
            
            self.schedule()
        }
        
        repeatingTimer?.resume()

奇怪的是,当我部署一个新的服务器版本(一个新的 Docker 容器)时,我发现至少在某些情况下,服务器的基于计时器的部分不会关闭。我已经采取了一些服务器端步骤(https://github.com/SyncServerII/ServerMain/issues/14),这些步骤在新部署时对服务器实例进行干净/完全重启以解决此问题,但它仍然很好奇我。似乎这些计划的计时器实例不应该在 Docker 容器更换后继续存在。

我的猜测是,我正在使用的计时器必须以某种方式有效地保留对正在运行的代码的引用,并且即使在新的 Docker 容器部署中也保留了该引用和计时器。

[对于那些可能会问的人:您为什么以这种方式使用计时器?我不打算长期这样做。我计划将服务器的第二部分转移到 AWS Lambda 之类的东西上,但还没有这样做]。

4

0 回答 0