diff --git a/src/compose/composition-steps.ts b/src/compose/composition-steps.ts index 787e3ec6..b0edce00 100644 --- a/src/compose/composition-steps.ts +++ b/src/compose/composition-steps.ts @@ -1,19 +1,17 @@ import _ from 'lodash'; import * as config from '../config'; - import type { Image } from './images'; import * as images from './images'; import type Network from './network'; import type Service from './service'; import * as serviceManager from './service-manager'; -import type Volume from './volume'; - -import { checkTruthy } from '../lib/validation'; import * as networkManager from './network-manager'; import * as volumeManager from './volume-manager'; -import type { DeviceLegacyReport } from '../types/state'; +import type Volume from './volume'; import * as commitStore from './commit'; +import { checkTruthy } from '../lib/validation'; +import type { DeviceLegacyReport } from '../types/state'; interface BaseCompositionStepArgs { force?: boolean; diff --git a/src/config/configJson.ts b/src/config/configJson.ts index a327640e..44fd87e1 100644 --- a/src/config/configJson.ts +++ b/src/config/configJson.ts @@ -4,7 +4,7 @@ import _ from 'lodash'; import * as constants from '../lib/constants'; import * as hostUtils from '../lib/host-utils'; import * as osRelease from '../lib/os-release'; -import { readLock, writeLock } from '../lib/update-lock'; +import { takeGlobalLockRO, takeGlobalLockRW } from '../lib/process-lock'; import type * as Schema from './schema'; export default class ConfigJsonConfigBackend { @@ -25,9 +25,9 @@ export default class ConfigJsonConfigBackend { this.schema = schema; this.writeLockConfigJson = () => - writeLock('config.json').disposer((release) => release()); + takeGlobalLockRW('config.json').disposer((release) => release()); this.readLockConfigJson = () => - readLock('config.json').disposer((release) => release()); + takeGlobalLockRO('config.json').disposer((release) => release()); } public async set(keyVals: { diff --git a/src/device-state.ts b/src/device-state.ts index ff4f7813..90fc1cd2 100644 --- a/src/device-state.ts +++ b/src/device-state.ts @@ -23,6 +23,7 @@ import { UpdatesLockedError, } from './lib/errors'; import * as updateLock from './lib/update-lock'; +import { takeGlobalLockRO, takeGlobalLockRW } from './lib/process-lock'; import * as dbFormat from './device-state/db-format'; import { getGlobalApiKey } from './device-api'; import * as sysInfo from './lib/system-info'; @@ -101,8 +102,6 @@ type DeviceStateStep = | deviceConfig.ConfigStep; let currentVolatile: DeviceReport = {}; -const writeLock = updateLock.writeLock; -const readLock = updateLock.readLock; let maxPollTime: number; let intermediateTarget: InstancedDeviceState | null = null; let applyBlocker: Nullable>; @@ -295,11 +294,11 @@ function emitAsync( } const readLockTarget = () => - readLock('target').disposer((release) => release()); + takeGlobalLockRO('target').disposer((release) => release()); const writeLockTarget = () => - writeLock('target').disposer((release) => release()); + takeGlobalLockRW('target').disposer((release) => release()); const inferStepsLock = () => - writeLock('inferSteps').disposer((release) => release()); + takeGlobalLockRW('inferSteps').disposer((release) => release()); function usingReadLockTarget any, U extends ReturnType>( fn: T, ): Bluebird> { @@ -803,7 +802,7 @@ export const applyTarget = async ({ function pausingApply(fn: () => any) { const lock = () => { - return writeLock('pause').disposer((release) => release()); + return takeGlobalLockRW('pause').disposer((release) => release()); }; // TODO: This function is a bit of a mess const pause = () => { diff --git a/src/device-state/target-state.ts b/src/device-state/target-state.ts index 86179676..6f0aa356 100644 --- a/src/device-state/target-state.ts +++ b/src/device-state/target-state.ts @@ -8,7 +8,7 @@ import type { TargetState } from '../types/state'; import { InternalInconsistencyError } from '../lib/errors'; import { getGotInstance } from '../lib/request'; import * as config from '../config'; -import { writeLock } from '../lib/update-lock'; +import { takeGlobalLockRW } from '../lib/process-lock'; import * as constants from '../lib/constants'; import log from '../lib/supervisor-console'; @@ -26,7 +26,7 @@ export const emitter: StrictEventEmitter = new EventEmitter(); const lockGetTarget = () => - writeLock('getTarget').disposer((release) => release()); + takeGlobalLockRW('getTarget').disposer((release) => release()); type CachedResponse = { etag?: string | string[]; diff --git a/src/lib/process-lock.ts b/src/lib/process-lock.ts new file mode 100644 index 00000000..a653cf7a --- /dev/null +++ b/src/lib/process-lock.ts @@ -0,0 +1,35 @@ +/** + * This module contains the functionality for locking & unlocking resources + * within the Supervisor Node process, useful for methods that need to acquire + * exclusive access to a resource across multiple ticks in the event loop, async + * functions for example. + * + * It is different from lockfile and update-lock modules, which handle + * inter-container communication via lockfiles. + * + * TODO: + * - Use a maintained solution such as async-lock + * - Move to native Promises + */ + +import Bluebird from 'bluebird'; +import Lock from 'rwlock'; +import type { Release } from 'rwlock'; + +type LockFn = (key: string | number) => Bluebird; + +const locker = new Lock(); + +export const takeGlobalLockRW: LockFn = Bluebird.promisify( + locker.async.writeLock, + { + context: locker, + }, +); + +export const takeGlobalLockRO: LockFn = Bluebird.promisify( + locker.async.readLock, + { + context: locker, + }, +); diff --git a/src/lib/update-lock.ts b/src/lib/update-lock.ts index 99705e10..b2e385b8 100644 --- a/src/lib/update-lock.ts +++ b/src/lib/update-lock.ts @@ -1,7 +1,5 @@ -import Bluebird from 'bluebird'; import { promises as fs } from 'fs'; import path from 'path'; -import Lock from 'rwlock'; import { isRight } from 'fp-ts/lib/Either'; import { @@ -13,6 +11,7 @@ import { pathOnRoot, pathExistsOnState } from './host-utils'; import * as config from '../config'; import * as lockfile from './lockfile'; import { NumericIdentifier, StringIdentifier, DockerName } from '../types'; +import { takeGlobalLockRW } from './process-lock'; const decodedUid = NumericIdentifier.decode(process.env.LOCKFILE_UID); export const LOCKFILE_UID = isRight(decodedUid) ? decodedUid.right : 65534; @@ -56,15 +55,6 @@ export function abortIfHUPInProgress({ }); } -type LockFn = (key: string | number) => Bluebird<() => void>; -const locker = new Lock(); -export const writeLock: LockFn = Bluebird.promisify(locker.async.writeLock, { - context: locker, -}); -export const readLock: LockFn = Bluebird.promisify(locker.async.readLock, { - context: locker, -}); - // Unlock all lockfiles of an appId | appUuid, then release resources. async function dispose( appIdentifier: string | number, @@ -195,7 +185,7 @@ export async function lock( for (const id of sortedIds) { const lockDir = pathOnRoot(lockPath(id)); // Acquire write lock for appId - releases.set(id, await writeLock(id)); + releases.set(id, await takeGlobalLockRW(id)); // Get list of service folders in lock directory const serviceFolders = await fs.readdir(lockDir).catch((e) => { if (ENOENT(e)) { diff --git a/src/logger.ts b/src/logger.ts index 5290901e..9a9b25f1 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -5,7 +5,7 @@ import * as config from './config'; import * as db from './db'; import * as eventTracker from './event-tracker'; import type { LogType } from './lib/log-types'; -import { writeLock } from './lib/update-lock'; +import { takeGlobalLockRW } from './lib/process-lock'; import type { LogBackend, LogMessage } from './logging'; import { BalenaLogBackend, LocalLogBackend } from './logging'; import type { MonitorHook } from './logging/monitor'; @@ -129,7 +129,7 @@ export function logSystemMessage( } export function lock(containerId: string): Bluebird.Disposer<() => void> { - return writeLock(containerId).disposer((release) => { + return takeGlobalLockRW(containerId).disposer((release) => { release(); }); }