Skip to content

Commit

Permalink
Merge pull request #2225 from botpress/ya-fix-require-cache
Browse files Browse the repository at this point in the history
fix(core): clearing cache of required actions and sync when remote
  • Loading branch information
allardy committed Aug 9, 2019
2 parents 86f64e3 + 5be2a8f commit 720cb7a
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 2 deletions.
32 changes: 31 additions & 1 deletion src/bp/core/services/action/action-service.ts
Expand Up @@ -4,6 +4,7 @@ import { UntrustedSandbox } from 'core/misc/code-sandbox'
import { printObject } from 'core/misc/print'
import { inject, injectable, tagged } from 'inversify'
import _ from 'lodash'
import ms from 'ms'
import path from 'path'
import { NodeVM } from 'vm2'

Expand All @@ -17,18 +18,47 @@ import { ActionMetadata, extractMetadata } from './metadata'
import { VmRunner } from './vm'

const debug = DEBUG('actions')
const SYNC_DEBOUNCE_DELAY = ms('2s')

@injectable()
export default class ActionService {
private _scopedActions: Map<string, ScopedActionService> = new Map()
private _syncDebounce

constructor(
@inject(TYPES.GhostService) private ghost: GhostService,
@inject(TYPES.ObjectCache) private cache: ObjectCache,
@inject(TYPES.Logger)
@tagged('name', 'ActionService')
private logger: Logger
) {}
) {
this._listenForCacheInvalidation()
this._syncDebounce = _.debounce(this._syncFiles, SYNC_DEBOUNCE_DELAY, { leading: true, trailing: false })
}

private _listenForCacheInvalidation() {
this.cache.events.on('invalidation', key => {
if (key.toLowerCase().indexOf(`/actions`) > -1) {
this._syncDebounce(key)
}
})
}

// Listening for sync here, because scopedActionService is not initialized until a user triggers an action
private async _syncFiles(key: string) {
const parts = key.split('/')
const botId = parts[parts.indexOf('actions') - 1]

Object.keys(require.cache)
.filter(r => r.match(/(\\|\/)actions(\\|\/)/g))
.map(file => delete require.cache[file])

if (botId === 'global') {
await this.ghost.global().syncDatabaseFilesToDisk('actions')
} else {
await this.ghost.forBot(botId).syncDatabaseFilesToDisk('actions')
}
}

forBot(botId: string): ScopedActionService {
if (this._scopedActions.has(botId)) {
Expand Down
13 changes: 13 additions & 0 deletions src/bp/core/services/ghost/service.ts
Expand Up @@ -365,6 +365,19 @@ export class ScopedGhostService {
await this.primaryDriver.moveFile(fromPath, toPath)
}

async syncDatabaseFilesToDisk(rootFolder: string): Promise<void> {
if (this.primaryDriver instanceof DiskStorageDriver) {
return
}

const remoteFiles = await this.dbDriver.directoryListing(this._normalizeFolderName(rootFolder))
const filePath = filename => this._normalizeFileName(rootFolder, filename)

await Promise.mapSeries(remoteFiles, async file =>
this.diskDriver.upsertFile(filePath(file), await this.dbDriver.readFile(filePath(file)))
)
}

async deleteFolder(folder: string): Promise<void> {
if (this.isDirectoryGlob) {
throw new Error(`Ghost can't read or write under this scope`)
Expand Down
16 changes: 15 additions & 1 deletion src/bp/core/services/hook/hook-service.ts
Expand Up @@ -3,8 +3,10 @@ import { IO } from 'botpress/sdk'
import { ObjectCache } from 'common/object-cache'
import { UntrustedSandbox } from 'core/misc/code-sandbox'
import { printObject } from 'core/misc/print'
import { WorkspaceUserAttributes } from 'core/repositories/workspace_users'
import { inject, injectable, tagged } from 'inversify'
import _ from 'lodash'
import ms from 'ms'
import path from 'path'
import { NodeVM } from 'vm2'

Expand All @@ -13,9 +15,9 @@ import { requireAtPaths } from '../../modules/require'
import { TYPES } from '../../types'
import { VmRunner } from '../action/vm'
import { Incident } from '../alerting-service'
import { WorkspaceUserAttributes } from 'core/repositories/workspace_users'

const debug = DEBUG('hooks')
const SYNC_DEBOUNCE_DELAY = ms('2s')

interface HookOptions {
timeout: number
Expand Down Expand Up @@ -128,6 +130,7 @@ class HookScript {
@injectable()
export class HookService {
private _scriptsCache: Map<string, HookScript[]> = new Map()
private _syncDebounce

constructor(
@inject(TYPES.Logger)
Expand All @@ -137,17 +140,28 @@ export class HookService {
@inject(TYPES.ObjectCache) private cache: ObjectCache
) {
this._listenForCacheInvalidation()
this._syncDebounce = _.debounce(this._syncFiles, SYNC_DEBOUNCE_DELAY, { leading: true, trailing: false })
}

private _listenForCacheInvalidation() {
this.cache.events.on('invalidation', key => {
if (key.toLowerCase().indexOf(`/hooks/`) > -1) {
// clear the cache if there's any file that has changed in the `hooks` folder
this._scriptsCache.clear()

this._syncDebounce()
}
})
}

private async _syncFiles() {
Object.keys(require.cache)
.filter(r => r.match(/(\\|\/)hooks(\\|\/)/g))
.map(file => delete require.cache[file])

await this.ghost.global().syncDatabaseFilesToDisk('hooks')
}

async executeHook(hook: Hooks.BaseHook): Promise<void> {
const scripts = await this.extractScripts(hook)
await Promise.mapSeries(_.orderBy(scripts, ['filename'], ['asc']), script => this.runScript(script, hook))
Expand Down

0 comments on commit 720cb7a

Please sign in to comment.