refactor(realtime): extract "update dirty note" to new job

Signed-off-by: BoHong Li <a60814billy@gmail.com>
This commit is contained in:
BoHong Li 2019-05-07 03:15:33 +08:00
parent 702fc48fa8
commit 0352057c8b
No known key found for this signature in database
GPG Key ID: 9696D5590D58290F
6 changed files with 211 additions and 124 deletions

View File

@ -1,30 +1,31 @@
'use strict'
// realtime
// external modules
var cookie = require('cookie')
var cookieParser = require('cookie-parser')
var url = require('url')
var async = require('async')
var randomcolor = require('randomcolor')
var Chance = require('chance')
var chance = new Chance()
var moment = require('moment')
const cookie = require('cookie')
const cookieParser = require('cookie-parser')
const url = require('url')
const async = require('async')
const randomcolor = require('randomcolor')
const Chance = require('chance')
const chance = new Chance()
const moment = require('moment')
const get = require('lodash/get')
// core
var config = require('./config')
var logger = require('./logger')
var history = require('./history')
var models = require('./models')
const config = require('./config')
const logger = require('./logger')
const history = require('./history')
const models = require('./models')
// ot
var ot = require('./ot')
const ot = require('./ot')
const {RealtimeClientConnection} = require('./realtimeClientConnection')
const { RealtimeClientConnection } = require('./realtimeClientConnection')
const { UpdateDirtyNoteJob } = require('./realtimeUpdateDirtyNoteJob')
// public
var realtime = {
const realtime = {
io: null,
onAuthorizeSuccess: onAuthorizeSuccess,
onAuthorizeFail: onAuthorizeFail,
@ -83,44 +84,20 @@ function emitCheck (note) {
// actions
var users = {}
var notes = {}
// update when the note is dirty
setInterval(function () {
async.each(Object.keys(notes), function (key, callback) {
var note = notes[key]
if (note.server.isDirty) {
if (config.debug) logger.info('updater found dirty note: ' + key)
note.server.isDirty = false
exports.updateNote(note, function (err, _note) {
// handle when note already been clean up
if (!notes[key] || !notes[key].server) return callback(null, null)
if (!_note) {
realtime.io.to(note.id).emit('info', {
code: 404
})
logger.error('note not found: ', note.id)
}
if (err || !_note) {
for (var i = 0, l = note.socks.length; i < l; i++) {
var sock = note.socks[i]
if (typeof sock !== 'undefined' && sock) {
setTimeout(function () {
sock.disconnect(true)
}, 0)
}
}
return callback(err, null)
}
note.updatetime = moment(_note.lastchangeAt).valueOf()
emitCheck(note)
return callback(null, null)
const updateDirtyNoteJob = new UpdateDirtyNoteJob(realtime)
updateDirtyNoteJob.start(realtime)
function disconnectSocketOnNote (note) {
note.socks.forEach((sock) => {
if (sock) {
sock.emit('delete')
setImmediate(() => {
sock.disconnect(true)
})
} else {
return callback(null, null)
}
}, function (err) {
if (err) return logger.error('updater error', err)
})
}, 1000)
}
function updateNote (note, callback) {
models.Note.findOne({
@ -858,6 +835,9 @@ exports.checkViewPermission = checkViewPermission
exports.getNoteFromNotePool = getNoteFromNotePool
exports.getUserFromUserPool = getUserFromUserPool
exports.buildUserOutData = buildUserOutData
exports.getNotePool = getNotePool
exports.emitCheck = emitCheck
exports.disconnectSocketOnNote = disconnectSocketOnNote
exports.notes = notes
exports.users = users
exports.disconnectSocketQueue = disconnectSocketQueue

View File

@ -57,17 +57,6 @@ class RealtimeClientConnection {
return config.allowAnonymous || config.allowAnonymousEdits
}
disconnectSocketOnNote (note) {
note.socks.forEach((sock) => {
if (sock) {
sock.emit('delete')
setImmediate(() => {
sock.disconnect(true)
})
}
})
}
getCurrentUser () {
if (!this.socket.id) return
return this.realtime.getUserFromUserPool(this.socket.id)
@ -206,7 +195,7 @@ class RealtimeClientConnection {
this.destroyNote(note.id)
.then((successRows) => {
if (!successRows) return
this.disconnectSocketOnNote(note)
this.realtime.disconnectSocketOnNote(note)
})
.catch(function (err) {
return logger.error('delete note failed: ' + err)

View File

@ -0,0 +1,50 @@
'use strict'
const async = require('async')
const config = require('./config')
const logger = require('./logger')
const moment = require('moment')
class UpdateDirtyNoteJob {
constructor (realtime) {
this.realtime = realtime
}
start () {
setInterval(this.updateDirtyNote.bind(this), 1000)
}
updateDirtyNote () {
const notes = this.realtime.getNotePool()
async.each(Object.keys(notes), (key, callback) => {
const note = notes[key]
if (!note.server.isDirty) return callback(null, null)
if (config.debug) logger.info('updater found dirty note: ' + key)
note.server.isDirty = false
this.realtime.updateNote(note, (err, _note) => {
// handle when note already been clean up
if (!notes[key] || !notes[key].server) return callback(null, null)
if (!_note) {
this.realtime.io.to(note.id).emit('info', {
code: 404
})
logger.error('note not found: ', note.id)
}
if (err || !_note) {
this.realtime.disconnectSocketOnNote(note)
return callback(err, null)
}
note.updatetime = moment(_note.lastchangeAt).valueOf()
this.realtime.emitCheck(note)
return callback(null, null)
})
}, (err) => {
if (err) return logger.error('updater error', err)
})
}
}
exports.UpdateDirtyNoteJob = UpdateDirtyNoteJob

View File

@ -0,0 +1,130 @@
/* eslint-env node, mocha */
'use strict'
const assert = require('assert')
const mock = require('mock-require')
const sinon = require('sinon')
const { removeModuleFromRequireCache, makeMockSocket } = require('./utils')
describe('realtime#update note is dirty timer', function () {
let realtime
let clock
beforeEach(() => {
clock = sinon.useFakeTimers({
toFake: ['setInterval']
})
mock('../../lib/logger', {
error: () => {
}
})
mock('../../lib/history', {})
mock('../../lib/models', {
Revision: {
saveAllNotesRevision: () => {
}
}
})
mock('../../lib/config', {})
realtime = require('../../lib/realtime')
realtime.io = {
to: sinon.stub().callsFake(function () {
return {
emit: sinon.fake()
}
})
}
})
afterEach(() => {
removeModuleFromRequireCache('../../lib/realtimeUpdateDirtyNoteJob')
removeModuleFromRequireCache('../../lib/realtime')
mock.stopAll()
clock.restore()
})
it('should update note when note is dirty', (done) => {
sinon.stub(realtime, 'updateNote').callsFake(function (note, callback) {
callback(null, note)
})
realtime.notes['note1'] = {
server: {
isDirty: false
},
socks: []
}
let note2 = {
server: {
isDirty: true
},
socks: []
}
realtime.notes['note2'] = note2
clock.tick(1000)
setTimeout(() => {
assert(note2.server.isDirty === false)
done()
}, 5)
})
it('should not do anything when note missing', function (done) {
sinon.stub(realtime, 'updateNote').callsFake(function (note, callback) {
delete realtime.notes['note']
callback(null, note)
})
let note = {
server: {
isDirty: true
},
socks: [makeMockSocket()]
}
realtime.notes['note'] = note
clock.tick(1000)
setTimeout(() => {
assert(note.server.isDirty === false)
assert(note.socks[0].disconnect.called === false)
done()
}, 50)
})
it('should disconnect all clients when update note error', function (done) {
sinon.stub(realtime, 'updateNote').callsFake(function (note, callback) {
callback(new Error('some error'), null)
})
realtime.io = {
to: sinon.stub().callsFake(function () {
return {
emit: sinon.fake()
}
})
}
let note = {
server: {
isDirty: true
},
socks: [makeMockSocket(), undefined, makeMockSocket()]
}
realtime.notes['note'] = note
clock.tick(1000)
setTimeout(() => {
assert(note.server.isDirty === false)
assert(note.socks[0].disconnect.called)
assert(note.socks[2].disconnect.called)
done()
}, 50)
})
})

View File

@ -37,67 +37,6 @@ function removeModuleFromRequireCache (modulePath) {
}
describe('realtime', function () {
describe('update note is dirty timer', function () {
let realtime
beforeEach(() => {
mock('../../lib/logger', {
error: () => {
}
})
mock('../../lib/history', {})
mock('../../lib/models', {
Revision: {
saveAllNotesRevision: () => {
}
}
})
mock('../../lib/config', {})
})
afterEach(() => {
removeModuleFromRequireCache('../../lib/realtime')
mock.stopAll()
})
it('should update note when note is dirty', (done) => {
const clock = sinon.useFakeTimers()
realtime = require('../../lib/realtime')
sinon.stub(realtime, 'updateNote').callsFake(function (note, callback) {
callback(null, null)
})
const socketIoEmitFake = sinon.fake()
realtime.io = {
to: sinon.stub().callsFake(function () {
return {
emit: socketIoEmitFake
}
})
}
realtime.notes['note1'] = {
server: {
isDirty: false
},
socks: []
}
let note2 = {
server: {
isDirty: true
},
socks: []
}
realtime.notes['note2'] = note2
clock.tick(1000)
clock.restore()
setTimeout(() => {
assert(note2.server.isDirty === false)
done()
}, 50)
})
})
describe('updateNote', function () {
let realtime, fakeNote
beforeEach(() => {

View File

@ -537,6 +537,5 @@ describe('realtime#socket event', function () {
done()
}, 5)
})
})
})