watchdog.ts 6.2 KB
Newer Older
1
/**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
2
 *   Wechaty - https://github.com/chatie/wechaty
3
 *
4
 *   @copyright 2016-2017 Huan LI <zixia@zixia.net>
5
 *
6 7 8
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
9
 *
10
 *       http://www.apache.org/licenses/LICENSE-2.0
11
 *
12 13 14 15 16
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
17
 *
18
 */
19 20
import * as os from 'os'

21
import {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
22 23 24
  WatchdogFood,
  WatchdogFoodName,
  log,
Huan (李卓桓)'s avatar
merge  
Huan (李卓桓) 已提交
25
}                   from '../config'
26

Huan (李卓桓)'s avatar
merge  
Huan (李卓桓) 已提交
27 28
import PuppetWeb    from './puppet-web'
import Event        from './event'
29

30
/* tslint:disable:variable-name */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
31
export const Watchdog = {
L
lijiarui 已提交
32
  onFeed,
33 34
}

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
35 36 37
/**
 * feed me in time(after 1st feed), or I'll restart system
 */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
38
function onFeed(this: PuppetWeb, food: WatchdogFood): void {
39 40 41 42 43 44 45
  if (!food.type) {
    food.type = 'HEARTBEAT'
  }
  if (!food.timeout) {
    food.timeout = 60000 // 60s default. can be override in options but be careful about the number zero(0)
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
46
  if (!this) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
47
    throw new Error('onFeed() must has `this` of instanceof PuppetWeb')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
48 49
  }

50
  log.silly('PuppetWebWatchdog', 'onFeed: %d, %s[%s]', food.timeout, food.type, food.data)
51

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
52 53 54 55 56 57
  if (food.type === 'POISON') {
    log.verbose('PuppetWebWatchdog', 'onFeed(type=POSISON) WANG! I dead!')
    clearWatchDogTimer.call(this)
    return
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
58 59 60 61 62 63 64 65 66 67 68 69
  /**
   * Disable Watchdog on the following conditions:
   * 1. current state is dead and inprocess
   * 1. target state is dead
   *
   * in other words, watchdog should only work in this condition:
   * 1. target state is live
   * 1. and stable is true
   *
   * this is because we will not want to active watchdog when we are closing a browser, or browser is closed.
   */
  if (this.state.target() === 'dead' || this.state.inprocess()) {
L
lijiarui 已提交
70 71 72
    log.warn('PuppetWebWatchdog', 'onFeed(type=%s, data=%s, timeout=%d) is disabled because state target:`%s` inprocess:`%s`',
                                  food.type, food.data, food.timeout,
                                  this.state.target(), this.state.inprocess(),
73
            )
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
74 75
    return
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
76

77
  const feed = `${food.type}:[${food.data}]`
78
  setWatchDogTimer.call(this, food.timeout, feed)
79

80
  this.emit('heartbeat', feed)
81

82
  monitorScan.call(this, food.type)
83
  autoSaveSession.call(this)
84
  memoryCheck.call(this)
85 86
}

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
87
function clearWatchDogTimer(this: PuppetWeb) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
88
  if (!this.watchDogTimer) {
89
    log.verbose('PuppetWebWatchdog', 'clearWatchDogTimer() nothing to clear')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
90
    return
91
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
92 93 94
  clearTimeout(this.watchDogTimer)
  this.watchDogTimer = null

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
95 96 97 98
  if (this.watchDogTimerTime) {
    const timeLeft = this.watchDogTimerTime - Date.now()
    log.silly('PuppetWebWatchdog', 'clearWatchDogTimer() [%d] seconds left', Math.ceil(timeLeft / 1000))
  }
99 100
}

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
101
function setWatchDogTimer(this: PuppetWeb, timeout: number, feed) {
102 103 104

  clearWatchDogTimer.call(this)

105
  log.silly('PuppetWebWatchdog', 'setWatchDogTimer(%d, %s)', timeout, feed)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
106
  this.watchDogTimer = setTimeout(_ => watchDogReset.call(this, timeout, feed), timeout)
107
  this.watchDogTimerTime = Date.now() + timeout
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
108
  // this.watchDogTimer.unref()
109 110 111
  // block quit, force to use quit() // this.watchDogTimer.unref() // dont block quit
}

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
112
async function watchDogReset(timeout, lastFeed): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
113
  log.verbose('PuppetWebWatchdog', 'watchDogReset(%d, %s)', timeout, lastFeed)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
114

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
115
  const e = new Error('watchDogReset() watchdog reset after '
116
                        + Math.floor(timeout / 1000)
117
                        + ' seconds, last feed:'
L
lijiarui 已提交
118
                        + '[' + lastFeed + ']',
119
                    )
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
120
  log.verbose('PuppetWebWatchdog', e.message)
121
  this.emit('error', e)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
122 123
  Event.onBrowserDead.call(this, e)
  return
124 125 126 127 128 129 130 131
}

/**
 *
 * Deal with Sessions(cookies)
 * save every 5 mins
 *
 */
132
async function autoSaveSession(this: PuppetWeb, force = false) {
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
133 134
  log.silly('PuppetWebWatchdog', 'autoSaveSession()')

135
  if (!this.userId) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
136
    log.verbose('PuppetWebWatchdog', 'autoSaveSession() skiped as no this.userId')
137 138 139
    return
  }

Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
140 141 142
  if (force) {
    this.watchDogLastSaveSession = 0 // 0 will cause save session right now
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
143 144

  const SAVE_SESSION_INTERVAL = 3 * 60 * 1000 // 3 mins
145
  if (Date.now() - this.watchDogLastSaveSession > SAVE_SESSION_INTERVAL) {
L
lijiarui 已提交
146 147 148
    log.verbose('PuppetWebWatchdog', 'autoSaveSession() profile(%s) after %d minutes',
                                     this.setting.profile,
                                     Math.floor(SAVE_SESSION_INTERVAL / 1000 / 60),
149
              )
150
    await this.browser.saveCookie()
151 152 153 154
    this.watchDogLastSaveSession = Date.now()
  }
}

155
function memoryCheck(this: PuppetWeb, minMegabyte: number = 4) {
156 157 158 159 160 161 162 163 164 165 166
  const freeMegabyte = Math.floor(os.freemem() / 1024 / 1024)
  log.silly('PuppetWebWatchdog', 'memoryCheck() free: %d MB, require: %d MB'
                                , freeMegabyte, minMegabyte)

  if (freeMegabyte < minMegabyte) {
    const e = new Error(`memory not enough: free ${freeMegabyte} < require ${minMegabyte} MB`)
    log.warn('PuppetWebWatchdog', 'memoryCheck() %s', e.message)
    this.emit('error', e)
  }

}
167 168 169 170 171 172 173 174 175
/**
 *
 * Deal with SCAN events
 *
 * if web browser stay at login qrcode page long time,
 * sometimes the qrcode will not refresh, leave there expired.
 * so we need to refresh the page after a while
 *
 */
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
176
function monitorScan(this: PuppetWeb, type: WatchdogFoodName) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
177
  log.silly('PuppetWebWatchdog', 'monitorScan(%s)', type)
178

179
  const scanTimeout = 4 * 60 * 1000 // 4 mins (before is 10 mins)
180 181 182

  if (type === 'SCAN') { // watchDog was feed a 'scan' data
    this.lastScanEventTime = Date.now()
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
183
    // autoSaveSession.call(this, true)
184 185
  }
  if (this.logined()) { // XXX: login status right?
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
186
    this.lastScanEventTime = 0
187 188
  } else if (this.lastScanEventTime
              && Date.now() - this.lastScanEventTime > scanTimeout) {
189 190
    log.warn('PuppetWebWatchdog', 'monirotScan() refresh browser for no food of type scan after %s mins'
                                , Math.floor(scanTimeout / 1000 / 60))
191 192 193 194 195
    // try to fix the problem
    this.browser.refresh()
    this.lastScanEventTime = Date.now()
  }
}
Huan (李卓桓)'s avatar
merge  
Huan (李卓桓) 已提交
196 197

export default Watchdog