-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from hyperifyio/dev
Dev to main
- Loading branch information
Showing
1,462 changed files
with
147,275 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
github: [heusalagroup] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
name: Run Tests | ||
|
||
on: | ||
push: | ||
workflow_dispatch: | ||
|
||
jobs: | ||
fetch-and-test: | ||
runs-on: self-hosted | ||
timeout-minutes: 30 | ||
steps: | ||
- uses: actions/checkout@v3 | ||
with: | ||
repository: heusalagroup/test | ||
submodules: recursive | ||
- name: set submodule branch | ||
run: | | ||
repo="${{ github.repository }}"; repo="${repo//./\/}"; repo="${repo/heusalagroup/}"; cd src$repo; | ||
git checkout ${{ github.ref_name }} | ||
- name: run tests | ||
run: NODE_ENV="dev" npm run test:ci |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Copyright (c) 2023. Heusala Group Oy <info@heusalagroup.fi>. All rights reserved. | ||
|
||
import { createAsyncLock } from "./AsyncLock"; | ||
|
||
describe('AsyncLock', () => { | ||
|
||
describe('createAsyncLock', () => { | ||
|
||
it('should create an instance of AsyncLock', () => { | ||
const asyncLock = createAsyncLock(); | ||
expect(typeof asyncLock).toBe('object'); | ||
}); | ||
|
||
it('should create a new instance each time it is called', () => { | ||
const asyncLock1 = createAsyncLock(); | ||
const asyncLock2 = createAsyncLock(); | ||
expect(asyncLock1).not.toBe(asyncLock2); | ||
}); | ||
|
||
it('should create an empty object', () => { | ||
const asyncLock = createAsyncLock(); | ||
expect(Object.keys(asyncLock).length).toBe(0); | ||
}); | ||
|
||
}); | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright (c) 2023. Heusala Group Oy <info@heusalagroup.fi>. All rights reserved. | ||
|
||
/** | ||
* Empty object used as a lock object. | ||
* | ||
* E.g. the internal memory reference is the ID of the lock. | ||
*/ | ||
export interface AsyncLock { | ||
} | ||
|
||
/** | ||
* Creates a new `AsyncLock` instance. | ||
*/ | ||
export function createAsyncLock () : AsyncLock { | ||
return {}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Copyright (c) 2023. Heusala Group Oy <info@heusalagroup.fi>. All rights reserved. | ||
|
||
/** | ||
* Service which synchronizes asynchronous operations | ||
*/ | ||
export interface AsyncSynchronizer { | ||
|
||
/** | ||
* Calls the provided callback and returns the result. | ||
* | ||
* If another call happens before the previous finishes it will wait for | ||
* the previous operation to finish before executing another asynchronous | ||
* callback. | ||
* | ||
* @param callback A async function which returns a promise | ||
*/ | ||
run<T> (callback: () => Promise<T>) : Promise<T>; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// Copyright (c) 2023. Heusala Group Oy <info@hg.fi>. All rights reserved. | ||
|
||
import { jest } from '@jest/globals'; | ||
import { AsyncSynchronizerImpl } from "./AsyncSynchronizerImpl"; | ||
import { LogLevel } from "./types/LogLevel"; | ||
|
||
describe('AsyncSynchronizerImpl', () => { | ||
|
||
let asyncSynchronizer: AsyncSynchronizerImpl; | ||
|
||
beforeAll(() => { | ||
AsyncSynchronizerImpl.setLogLevel(LogLevel.NONE); | ||
}); | ||
|
||
beforeEach(() => { | ||
asyncSynchronizer = AsyncSynchronizerImpl.create(); | ||
}); | ||
|
||
describe('create', () => { | ||
it('should create an instance of AsyncSynchronizerImpl', () => { | ||
expect(asyncSynchronizer).toBeInstanceOf(AsyncSynchronizerImpl); | ||
}); | ||
}); | ||
|
||
describe('run', () => { | ||
|
||
it('should return the result of the callback', async () => { | ||
const expectedResult : string = 'Test'; | ||
const callback = jest.fn<() => Promise<string>>().mockResolvedValue(expectedResult); | ||
|
||
const result = await asyncSynchronizer.run<string>(callback); | ||
|
||
expect(result).toBe(expectedResult); | ||
expect(callback).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should execute the callbacks in order', async () => { | ||
let callbackOrder: number[] = []; | ||
const callback1 = jest.fn(() => new Promise(resolve => setTimeout(() => resolve(callbackOrder.push(1)), 200))); | ||
const callback2 = jest.fn(() => new Promise(resolve => setTimeout(() => resolve(callbackOrder.push(2)), 100))); | ||
asyncSynchronizer.run(callback1); | ||
asyncSynchronizer.run(callback2); | ||
await new Promise(resolve => setTimeout(resolve, 400)); | ||
expect(callbackOrder).toEqual([1, 2]); | ||
}); | ||
|
||
}); | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// Copyright (c) 2023. Heusala Group Oy <info@heusalagroup.fi>. All rights reserved. | ||
|
||
import { first } from "./functions/first"; | ||
import { LogService } from "./LogService"; | ||
import { AsyncSynchronizer } from "./AsyncSynchronizer"; | ||
import { AsyncLock, createAsyncLock } from "./AsyncLock"; | ||
import { LogLevel } from "./types/LogLevel"; | ||
|
||
const LOG = LogService.createLogger( 'AsyncSynchronizerImpl' ); | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
export class AsyncSynchronizerImpl implements AsyncSynchronizer { | ||
|
||
private readonly _queue : AsyncLock[]; | ||
private _waitLockRelease : Promise<void> | undefined; | ||
private _releaseLockQueue : (() => void) | undefined; | ||
|
||
protected constructor () { | ||
this._queue = []; | ||
this._waitLockRelease = undefined; | ||
this._releaseLockQueue = undefined; | ||
} | ||
|
||
public static setLogLevel (level : LogLevel) : void { | ||
LOG.setLogLevel(level); | ||
} | ||
|
||
/** | ||
* Create instance of the AsyncSynchronizer | ||
*/ | ||
public static create () : AsyncSynchronizerImpl { | ||
return new AsyncSynchronizerImpl(); | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
public async run<T = any> (callback: () => Promise<T>) : Promise<T> { | ||
|
||
// Create a lock for this request | ||
const lock : AsyncLock = createAsyncLock(); | ||
LOG.debug(`Created a lock. This lock queue has ${this._queue.length} locks.`); | ||
|
||
// Put this request to the queue | ||
this._queue.push(lock); | ||
LOG.debug(`Added our lock to the queue`); | ||
|
||
// Wait for a lock | ||
LOG.debug(`Waiting for us to be the first in the queue`); | ||
while ( this._queue.length && first(this._queue) !== lock ) { | ||
|
||
LOG.debug(`The queue did not have us, waiting ${this._queue.length} locks...`); | ||
|
||
if (this._waitLockRelease === undefined) { | ||
let release : boolean = false; | ||
if (this._releaseLockQueue !== undefined) { | ||
this._releaseLockQueue(); | ||
this._releaseLockQueue = () => { | ||
release = true; | ||
}; | ||
} | ||
this._waitLockRelease = new Promise<void>( (resolve) => { | ||
if (release) { | ||
this._releaseLockQueue = undefined; | ||
resolve(); | ||
} else { | ||
this._releaseLockQueue = resolve; | ||
} | ||
}); | ||
} | ||
|
||
await this._waitLockRelease; | ||
|
||
} | ||
|
||
if (first(this._queue) !== lock) { | ||
throw new TypeError(`Could not acquire lock to the queue`); | ||
} | ||
|
||
LOG.debug(`Lock acquired to the queue and calling the callback`); | ||
let result : T; | ||
try { | ||
result = await callback(); | ||
} finally { | ||
// Release the lock | ||
if ( first(this._queue) !== lock ) { | ||
LOG.warn(`Warning! The lock queue did not have us as the first item. Could not release the lock.`); | ||
} else { | ||
this._queue.shift(); | ||
LOG.debug(`Released the lock from queue`); | ||
} | ||
|
||
if (this._releaseLockQueue !== undefined) { | ||
this._releaseLockQueue(); | ||
this._releaseLockQueue = undefined; | ||
LOG.debug(`Released the lock queue for next lock processing`); | ||
} | ||
|
||
} | ||
return result; | ||
|
||
} | ||
|
||
} |
Oops, something went wrong.