Skip to content

Commit

Permalink
Merged code from fi.hg.core and related modules to the new Hyperify F…
Browse files Browse the repository at this point in the history
…ramework
  • Loading branch information
Jaakko Heusala committed Dec 11, 2023
1 parent 027d8d8 commit f90647e
Show file tree
Hide file tree
Showing 1,442 changed files with 141,688 additions and 32 deletions.
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github: [heusalagroup]
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
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
36 changes: 6 additions & 30 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
Expand Down Expand Up @@ -42,8 +41,8 @@ build/Release
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript v1 declaration files
typings/

# TypeScript cache
*.tsbuildinfo
Expand All @@ -54,9 +53,6 @@ web_modules/
# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
Expand All @@ -72,41 +68,29 @@ web_modules/
# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
# dotenv environment variables file
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

Expand All @@ -119,12 +103,4 @@ dist
# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.idea
27 changes: 27 additions & 0 deletions AsyncLock.test.ts
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);
});

});

});
16 changes: 16 additions & 0 deletions AsyncLock.ts
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 {};
}
19 changes: 19 additions & 0 deletions AsyncSynchronizer.ts
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>;

}
48 changes: 48 additions & 0 deletions AsyncSynchronizerImpl.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) 2023. Heusala Group Oy <info@hg.fi>. All rights reserved.

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]);
});

});

});
106 changes: 106 additions & 0 deletions AsyncSynchronizerImpl.ts
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;

}

}
Loading

0 comments on commit f90647e

Please sign in to comment.