vapor/vapor

Object 0x7f5ed8607740 deallocated with retain count 2, reference may have escaped from deinit.

shenfu1991 opened this issue · 5 comments

Describe the bug

After running for about a month, I suddenly found that the program crashed. Checking the log showed: object 0x7f5ed8607740 deallocated with retain count 2, reference may have escaped from deinit.

To Reproduce

I don't know how to reproduce it, on the same server I run the entire project source code exactly the same and they all run fine. I will attach the source code of this class that was destroyed.

Expected behavior

Runs stably without crashing

Environment

  • Vapor Framework version: 4.76.2
  • Vapor Toolbox version: main (339fb04)
  • OS version: ubuntu server 20.04

Additional context

Add any other context about the problem here.

full project log: https://osx1.xuanyuanhuangdi.org/lo.zip

XBotViewController.swift
//
//  XBotViewController.swift
//  
//
//  Created by xuanyuan on 2021/3/5.
//

import Vapor
import Foundation

struct JuguCoreCase {
    var chanStatus = 0
    var chanDesc = ""
    
    var pkPlus = 0
    var pkPlusDesc = ""
    var pkPlusEMA = 0

    var pkt = 0
    var pktDesc = ""
    var pktEMA = 0
    
    var ema = 0
    var emaExtra = 0
    var emaDesc = ""
    
    var coreNum = 0
    var maLow: Double = 0
    
    var pk = 0
//    var rsi: Double = 0
//    var so: Double = 0
//    var cci: Double = 0
//    var psy: Double = 0
//    var CHOP: Double = 0
    var color = ""
    var coreEma: Double = 0
    var vol: Double = 0
    var volatility: Double = 0
    var sharpe: (Double,Double,Double,Int) = (0,0,0,0)
    var TT = 0
    var SB20 = 0
    var WT: (Int,Double,Double,Int,Int,Int,Int) = (0,0,0.0,0,0,0,0)
    var isRun = false
    var run: Double = 0
    var bc = 0
    var mn: (Int,String) = (0,"")
}

class XBotViewController {
    let formatter: DateFormatter = {
        let format = DateFormatter()
        format.dateFormat = "dd"
        format.timeZone = TimeZone(identifier: TimeZone.current.identifier)
        return format
    }()
    
    let dformatter: DateFormatter = {
        let format = DateFormatter()
        format.dateFormat = "yyyy-MM-dd HH:mm"
        format.timeZone = TimeZone(identifier: TimeZone.current.identifier)
        return format
    }()
    var userName = "shenfu"
    var API_Key = "***"
    var API_SecretKey = "***"
    var fHost = "https://fapi.binance.com"
    var kSingleOpen = 0.0
    var kStock = 0.0
    var kSymbol = "BTCUSDT"
    var kIsSimulation = true
    var total = 0.0
    var balance: Double = 2000
    var availableBalance: Double = 0
    var maxBalance: Double = 0
    var crossWalletBalance: Double = 0
    var sum = 0.0
    var closeTime = 0
    var stockAvg = 0.0
    var current = 0.0
    var canGo = false
    var last = 0.0
    var zero = 0.0
    var isPendingClose = false
    var isPendingOpen = false
    var fetchError = false
    var logTextArr: [String] = []
    var closeLogArr: [String] = []
    let manager = NetwokingManager()
    let httpTool = CoreNetwokingManager.sharedInstance
    var closeInfo: [String: Int] = [:]
    var earnInfo: [String: Double] = [:]
    var openTime = 0
    var autoStoploss = true //是否自动止损
    var isDealloc = false //销毁
    var isLong = false
    var isShort = false
    var featureStatus: FeatureStatus = .none
    var subStatus: FeatureStatus = .none
    var featureEarn: Double = 0
    var maxEarn: Double = 0
    var shortTime = 0
    var shortSum: Double = 0
    var featureX: Double = 0.5
    var shouldLong = false
    var firstOpenTimeStamp: TimeInterval = 0
    var openTimeStampArr: [String] = []
    var bootTimeStamp: TimeInterval = 0
    var maxSum: Double = 0
    var minSum: Double = 0
    var interval = "5m"
    var flagDesc = ""
    var earnTime = 0
    let systemVersion = 20.38
    let versionMsg = """

           选择比努力重要!
          
           概率决定了一切!

           开车需要目视远方

        数学期望,赌场盈利原理。

           用魔法打败魔法!

           AI ---> money
     

"""
    var allDatas: [MACDModel] = []
    var shoudStopEarn = false
    var maxFloatEarn: Double = 0 //最大浮盈
    var maxFloatEarnDic: [String: Any] = [:] //最大浮盈记录
    var tradeInfoArr: [tradeInfo] = [] 
    let server = getCurrentServer()
    var flagTimeStamp: Double = 0
    var flagInfo: [String: Any] = [:]
    var keepOrigialBalance = true
    var origialBalance: Double = 2000
    var isNew = false
    var preCloseIsLong = false
    var canTrade = false
    var openInCoreNum = 0
    var maxFloatLoss: Double = 0
    var shouldStop = false
    var shouldWaitUpdate = false
    var feeSum: Double = 0
    var shouldAutoFitFeatureX = true
    var dualSidePositionTimes = 0
    var fetchAssetSuccess = false
    var juguCoreValue = JuguCoreCase()
    var adaptX = 0
    var adaptXTimes = 0
    var lastestLog = ""
    var openTimes = 0
    var functionName = ""
//    var traderInfos: [String: String] = [:]
    var isAutoFollow = false
    var fetchTraderOk = false
    var isWaitingResume = false
    var realTname = ""
    var RealNameInfo: [String: String] = [:]
    var penddingBindConfirmTimestamp: TimeInterval = 0
    var penddingRemoveConfirmTimestamp: TimeInterval = 0
    var preBTC: Double = 0
    var preBTCTime = 0
    var watchingTimeStamp: TimeInterval = 0
    var removeSymbol = ""
    var currentEarnRate: Double = 0
    var maxEarnRate: Double = 0
    var sendRetryTimes = 0
    var csvString = ""
    var shouldRecordCSV = false
    var hasClearData = false
    var preArr: [Double] = []
    var isHalf = false
    var earnPercent: Double = 0
    var penddingDealloc = false

    func boot() {
        bootTimeStamp = Date().timeIntervalSince1970
        manager.API_Key = API_Key
        httpTool.API_Key = API_Key
        fetchFeatureInstock(loop: true)
        loadFeatureBalance()
        loadPrice()
        if isNew {
            DispatchQueue.global().asyncAfter(deadline: .now()+8) {
                self.watching()
            }
        }else{
            watching()
        }
        if isAutoFollow {
            goAutoFollow()
            wtRealT()
        }else{
            selectFunc()
        }
        addLog("正在启动 \(server.rawValue)... interval: \(interval)  boot...")
        
        positionSide()
        JuGuCore()
        gohandleDatas()
        sendCSV()
        addPresous()
    }
        
    func selectMd(ss: [String],v: Int) {
       
        var fc = TCMD1
        if v == 2 {
            fc = TCMD2
        }else if v == 3 {
            fc = TCMD3
        }else if v == 4 {
            fc = TCMD4
        }else if v == 5 {
            fc = TCMD5
        }else if v == 6 {
            fc = TCMD6
        }else if v == 7 {
            fc = TCMD7
        }else if v == 8 {
            fc = TCMD8
        }else if v == 9 {
            fc = TCMD9
        }else if v == 10 {
            fc = TCMD10
        }else if v == 11 {
            fc = TCMD11
        }else if v == 12 {
            fc = TCMD12
        }
       
        let funcArr = [
            fc
        ]
        var fcIdx = 0
        var itIdx = 0
        let itArr = ["3m","5m","15m","30m","1h","4h"]
        for (_,value) in ss.enumerated() {
            let fc = funcArr[fcIdx]
            if server.rawValue == value {
                fc()
                self.interval = itArr[itIdx]
            }
            fcIdx += 1
            if fcIdx >= funcArr.count {
                fcIdx = 0
                itIdx += 1
                if itIdx >= itArr.count {
                    itIdx = 0
                }
            }
//            debugPrint("\(value)=\(itArr[itIdx]),\(fc.self)")
        }
    }
    
    func common(ss: [String]) {
        
        let funcArr = [
            TC2P
        ]
        var fcIdx = 0
        var itIdx = 0
        let itArr = ["3m","5m","15m","30m","1h","4h"]
        for (_,value) in ss.enumerated() {
            let fc = funcArr[fcIdx]
            if server.rawValue == value {
                fc()
                self.interval = itArr[itIdx]
            }
            fcIdx += 1
            if fcIdx >= funcArr.count {
                fcIdx = 0
                itIdx += 1
                if itIdx >= itArr.count {
                    itIdx = 0
                }
            }
            //            debugPrint("\(value)=\(itArr[itIdx]),\(fc.self)")
        }
    }
    
    func getSS(name: String) ->[String] {
        let ss = RunServer.allCases.map { rs in
            rs.rawValue
        }.filter { str in
            str.contains(name)
        }
        return ss
    }
        
    func selectFunc() {
        
        shouldRecordCSV = server.rawValue.contains("USX")
        
        let s1 = getSS(name: "OSX")
        selectMd(ss: s1, v: 1)
        
        let s2 = getSS(name: "OSY")
        selectMd(ss: s2, v: 2)
        
        let s3 = getSS(name: "OSZ")
        selectMd(ss: s3, v: 3)
        
        let s4 = getSS(name: "TXX")
        selectMd(ss: s4, v: 4)
        
        let s5 = getSS(name: "TXY")
        selectMd(ss: s5, v: 5)
        
        let s6 = getSS(name: "TXZ")
        selectMd(ss: s6, v: 6)
        
        let s7 = getSS(name: "SGX")
        selectMd(ss: s7, v: 7)
        
        let s8 = getSS(name: "SGY")
        selectMd(ss: s8, v: 8)
        
        let s9 = getSS(name: "XGX")
        selectMd(ss: s9, v: 9)
        
        let s10 = getSS(name: "XGY")
        selectMd(ss: s10, v: 10)
        
        let s11 = getSS(name: "JPY")
        selectMd(ss: s11, v: 11)
        
        let s12 = getSS(name: "FRZ")
        selectMd(ss: s12, v: 12)
        
        let s13 = getSS(name: "USX")
        common(ss:s13)
                
    }

    
    func updateDomainSysTem() {
        shell("./MENG")
    }
    
    @discardableResult
    func shell(_ args: String...) -> Int32 {
        let task = Process()
        task.launchPath = "/usr/bin/env"
        task.arguments = args
        task.launch()
//        task.waitUntilExit()
        return 0
    } 
    
    func loadPrice() {
        if isDealloc {
            return
        }
        let p = getCurrentPrice(kSymbol)
        if p > 0 {
            self.current = p
        }
        DispatchQueue.global().asyncAfter(deadline: .now()+0.5) {
            self.loadPrice()
        }
    }
    
    func isMatchMinX() -> Bool {
        return featureEarn > self.balance/2000.0*self.featureX*20
    }
    
    func getX() -> Double {
        return 100000
    }
    
    func getZ() -> Double {
        return getX()
    }
    
    func watching() {
        if isDealloc {
            return
        }
        
        if penddingDealloc {
            isLong = false
            isShort = false
        }
        
        if juguCoreValue.volatility == 0 && kIsSimulation && !isAutoFollow {
            DispatchQueue.global().asyncAfter(deadline: .now()+2) {
                self.watching()
            }
            return
        }
        
        let now = Date().timeIntervalSince1970
        
        if watchingTimeStamp > 0 {
            if now - watchingTimeStamp < 1.5 {
                self.addLog("看门狗异常")
                return
            }
        }
        
        if kIsSimulation {
            if balance < 500 {
                balance += 2000
                self.addLog("赌本不足,系统给您补充...")
            }
        }
        
        watchingTimeStamp = now
               
        if balance <= 0 || (featureEarn < 0 && balance + featureEarn <= 0) || (getEarn() + balance <= 0) {
            addLog("阵亡!")
            debugPrint("阵亡!")
            isDealloc = true
        }
        
        if kStock > 0 {
            featureEarn = (current - stockAvg) * kStock
            currentEarnRate = (current-stockAvg)/stockAvg
        }else if kStock < 0 {
            featureEarn = (stockAvg - current) * fabs(kStock)
            currentEarnRate = (stockAvg-current)/current
        }else{
            featureEarn = 0
            currentEarnRate = 0
        }
        
        if featureEarn > maxEarn {
            maxEarn = featureEarn
        }
        
        if currentEarnRate > maxEarnRate {
            maxEarnRate = currentEarnRate
        }
        
        autoFitX()
        
        if featureEarn > maxFloatEarn {
            maxFloatEarn = featureEarn
            maxFloatEarnDic["maxFloatEarn"] = maxFloatEarn
            maxFloatEarnDic["maxFloatEarnTimeStamp"] = Date().timeIntervalSince1970
        }
        
        if featureEarn < maxFloatLoss && featureEarn < 0 {
            maxFloatLoss = featureEarn
            maxFloatEarnDic["maxFloatLoss"] = maxFloatLoss
        }
      
        if kIsSimulation {
            if maxEarnRate >= 0.0125*3 && currentEarnRate <= maxEarnRate*0.66 && currentEarnRate > 0 {
                earnPercent = (currentEarnRate/maxEarnRate)
                shoudStopEarn = true
            }else if maxEarnRate >= 0.0125*2 && currentEarnRate <= maxEarnRate*0.7 && currentEarnRate > 0 {
                earnPercent = (currentEarnRate/maxEarnRate)
                shoudStopEarn = true
            }
        }
                
        var side = ""
        if kStock == 0 {
            if isLong {
                side = "做多"
            }else{
                side = "做空"
            }
        }else{
            if kStock > 0 {
                side = "做多"
            }else{
                side = "做空"
            }
        }
        
        if shouldAutoClose() { // 止损
            addLog("貌似方向错了,系统为您自动平仓...,当前差价\(stockAvg - current)")
            canGo = false
            last = current
            isPendingClose = true
            addLog("\n\(kSymbol)\(side)】第一次命中平仓价格,当前价格:\(current),尝试刷新价格...\n")
            refreshClose()
        }else if isPendingOpen { //刷新购买价格
            canGo = false
            refreshOpen()
        }else if isPendingClose { //刷新卖价
            canGo = false
            refreshClose()
        }else if shouldOpen() { //开仓
            last = current
            isPendingOpen = true
            shouldLong = side == "做多"
            addLog("\n\(kSymbol)\(side)】第一次命中开仓价格,当前价格:\(current),尝试刷新价格...\n")
            canGo = false
            refreshOpen()
        }else if shouldClose() { //平仓
            if !isAutoFollow {
                self.flagDesc += "\n\n 【close】\n: " + lastestLog + "\n"
            }
            canGo = false
            last = current
            isPendingClose = true
            addLog("\n\(kSymbol)\(side)】第一次命中平仓价格,当前价格:\(current),尝试刷新价格...\n")
            refreshClose()
        }else {
            if shouldWaitUpdate {
                DispatchQueue.global().asyncAfter(deadline: .now()+30) {
                    self.watching()
                }
            }else{
                DispatchQueue.global().asyncAfter(deadline: .now()+2) {
                    self.watching()
                }
            }
        }
    }
    
    func autoFitX() {
        if !kIsSimulation  {
            let avs = vcArr.filter({ vc in
                vc.kIsSimulation && !vc.isAutoFollow
            })
            
            let bingoArr = avs.filter { vc in
                vc.currentEarnRate >= 0.0125
            }
            
            let isBull = Double(bingoArr.count)/Double(avs.count) >= 0.25 //正在赚钱的b超一半
            
            if isBull || currentEarnRate >= 0.05 {
                featureX = 0.75
            }else{
                featureX = 0.5
            }
        }
    }
    
    func openNow(vol: String? = nil) {
        if openTime == 0 {
            if let times = self.flagInfo["\(self.flagTimeStamp)"] as? Int {
                self.flagInfo["\(self.flagTimeStamp)"] = times + 1
            }else{
                self.flagInfo["\(self.flagTimeStamp)"] = 1
            }
        }
        if firstOpenTimeStamp == 0 {
            firstOpenTimeStamp = Date().timeIntervalSince1970
        }
     
        isWaitingResume = false
        let fee1 = fabs(kStock)*stockAvg*0.0004
        sum -= fee1
        feeSum += fee1
        balance -= fee1
        
        if !self.kIsSimulation && isAutoFollow {
            self.sendRealT()
        }
        
        let t = self.getST(stamp: Date().timeIntervalSince1970*1000)
        let times = openTimeStampArr.count + 1
        openTimeStampArr.append("\n\(times)\n\(t)\nprice=\(current)\nvol=\(kSingleOpen)\navg=\(stockAvg)\n")
        
        openTime += 1
        if kIsSimulation {
            openTimes += 1
        }
        addLog("\n\(kSymbol)开仓成功,当前价格:\(current)\n")
        isPendingOpen = false
        if kIsSimulation {
            DispatchQueue.global().asyncAfter(deadline: .now()+5) {
                self.watching()
            }
        }else{
            DispatchQueue.global().asyncAfter(deadline: .now()+13) {
                self.watching()
            }
        }
       
    }
        
    func closeNow() {
                
        maxFloatEarn = 0
        maxFloatLoss = 0
        openTimes = 0
        currentEarnRate = 0
        maxEarnRate = 0
        shoudStopEarn = false
        isWaitingResume = false
        penddingRemoveConfirmTimestamp = 0
        
        maxEarn = 0
        featureStatus = .none
        subStatus = .none
        openTime = 0
        let d = formatter.string(from: Date())
        closeTime += 1
        let times = closeInfo[d]
        if let t = times {
            closeInfo[d] = t + 1
        }else{
            closeInfo[d] = 1
        }
        
        var msg = ""
        
        if featureEarn > 0 {
            earnTime += 1
        }
        
        sum += featureEarn
        let earns = earnInfo[d]
        if let e = earns {
            earnInfo[d] = e + featureEarn
        }else{
            earnInfo[d] = featureEarn
        }
        let fee = fabs(kStock)*stockAvg*0.001
        sum -= fee
        feeSum += fee
        let sts = featureEarn > 0 ? "٩(^ᴗ^)۶赚钱了" : "٩(^ᴗ^)۶再接再厉"
        let side = kStock > 0 ? "做多" : "做空"
        msg = """
         \(sts)
          用户:\(userName)
          余额:\(balance)
          盈利:\(featureEarn)
\(kSymbol)合约\(side)】平仓成功
          x: \((fabs(kStock)*stockAvg/balance).strBTF())
         函数名:\(functionName)_\(interval)
         当前价格:\(current)
         持仓均价:\(stockAvg)
         数量:\(kStock)
         \(String(format: "累计收益:%.4f", sum))
         "持仓金额:\(fabs(kStock)*stockAvg/featureX)
         手续费:\(fee.str2F())
         涨跌幅:\((((current-stockAvg)/stockAvg)*100).str2F())
         """
        
        if !kIsSimulation {
            sendMsg(text: msg)
        }
        
        let c = Date().timeIntervalSince1970
        let maxFloat = maxFloatEarnDic["maxFloatEarn"] as? Double ?? 0
        let maxFloatLoss1 = maxFloatEarnDic["maxFloatLoss"] as? Double ?? 0
        let maxFloatEarnTimeStamp = maxFloatEarnDic["maxFloatEarnTimeStamp"] as? Double ?? 0
        let tmp = tradeInfo(firstOpenTimeStamp: firstOpenTimeStamp, closeTime: c, featureEarn: featureEarn, maxFloatEarn: maxFloat, maxFloatEarnTimeStamp: maxFloatEarnTimeStamp, maxFloatLoss: maxFloatLoss1,isLong: kStock > 0,openInCoreNum: openInCoreNum)
        tradeInfoArr.append(tmp)
        maxFloatEarnDic = [:]
        openTimeStampArr = []
        firstOpenTimeStamp = 0
        
        if kStock < 0 {
            shortTime += 1
            shortSum += featureEarn
        }
        if kIsSimulation {
            balance += featureEarn
            kStock = 0
            stockAvg = 0
            balance -= fee
        }else{
            kStock = 0
            stockAvg = 0
        }
        
        if sum > maxSum {
            maxSum = sum
        }
        
        if sum < minSum {
            minSum = sum
        }
                    
        let closeMsg = flagDesc + "\n~~~~~~~~~~~~~\n" + msg + "\n\n\n\n\n"
        addCloseLog(closeMsg)
        addLog(msg)
        isPendingClose = false
        featureEarn = 0
        flagDesc = ""
        if kIsSimulation {
            DispatchQueue.global().asyncAfter(deadline: .now()+5) {
                self.watching()
            }
        }else{
            DispatchQueue.global().asyncAfter(deadline: .now()+23) {
                self.watching()
            }
            if !kIsSimulation && isAutoFollow {
                removeRealT()
            }
        }
    }
    
    deinit {
        debugPrint("\(self) 实盘销毁:\(userName) -deinit")
    }
}
vzsg commented

None of the code in that file seems to be even remotely related to Vapor. That includes the crash: one of your classes, not necessarily this one, has a deinit which illegally captured self – for example in a DispatchQueue.async closure that the code is seemingly infested with.

This is the result of a misuse of Dispatch causing an object to be resurrected during its deinit; it has nothing to do with Vapor.

mkll commented

@shenfu1991 It's very strange that your code crashes only once a month. Due to your illiterate use of the GCD, it should crash every few minutes.

@mkll This is indeed very strange. It doesn't crash every month, and I'm not sure when it crashes. I have encountered this several times. I will fix the error in GCD and observe again. Thank you to the individuals mentioned above for pointing it out.

Actually, this class should never be destroyed because I haven't discarded it. It is an infinite loop task.