├── .github └── workflows │ └── build.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── package.json ├── prettier.config.js ├── rollup.config.js ├── src ├── MutexAcceptor.ts ├── MutexConnector.ts ├── MutexServer.ts ├── client │ ├── RemoteBarrier.ts │ ├── RemoteConditionVariable.ts │ ├── RemoteLatch.ts │ ├── RemoteMutex.ts │ ├── RemoteSemaphore.ts │ └── index.ts ├── index.ts ├── module.ts └── server │ ├── GlobalGroup.ts │ ├── ProviderGroup.ts │ ├── components │ ├── IComponent.ts │ ├── ServerBarrier.ts │ ├── ServerConditionVariable.ts │ ├── ServerLatch.ts │ ├── ServerMutex.ts │ ├── ServerSemaphore.ts │ ├── SolidComponent.ts │ └── internal │ │ ├── Disolver.ts │ │ └── Joiner.ts │ ├── global │ ├── GlobalBarriers.ts │ ├── GlobalBase.ts │ ├── GlobalConditionVariables.ts │ ├── GlobalLatches.ts │ ├── GlobalMutexes.ts │ └── GlobalSemaphores.ts │ └── providers │ ├── BarriersProvider.ts │ ├── ConditionVariablesProvider.ts │ ├── LatchesProvider.ts │ ├── MutexesProvider.ts │ ├── ProviderBase.ts │ └── SemaphoresProvider.ts ├── test ├── condition_variables │ ├── test_condition_variable_disconnections.ts │ └── test_condition_variable_waits.ts ├── index.ts ├── internal │ ├── ConnectionFactory.ts │ ├── IActivation.ts │ └── Validator.ts ├── manual │ └── remote-mutex.ts ├── mutextes │ ├── test_mutex_disconnections.ts │ └── test_mutex_locks.ts ├── semaphores │ ├── test_semaphore_acquires.ts │ ├── test_semaphore_disconnections.ts │ └── test_semaphore_simple.ts ├── test_destructors.ts └── tsconfig.json └── tsconfig.json /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | Ubuntu: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - uses: actions/setup-node@v4 10 | with: 11 | node-version: 20.x 12 | - run: npm install 13 | - run: npm run build 14 | - run: npm run test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | lib/ 3 | node_modules/ 4 | 5 | package-lock.json 6 | pnpm-lock.yaml 7 | *.log -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceRoot}/bin/test/main.js", 12 | "cwd": "${workspaceRoot}", 13 | // TypeScript 14 | "sourceMaps": true 15 | }, 16 | { 17 | "type": "node", 18 | "request": "attach", 19 | "name": "Attach to Process", 20 | "port": 5858, 21 | "outFiles": [] 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.formatOnSave": true, 4 | "[typescript][javascript]": { 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "editor.codeActionsOnSave": { 7 | "source.fixAll.eslint": "explicit" 8 | }, 9 | } 10 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at samchon@samchon.org. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jeongho Nam 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mutex Server 2 | ## 1. Outline 3 | ```bash 4 | npm install --save mutex-server 5 | ``` 6 | 7 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/samchon/mutex/blob/master/LICENSE) 8 | [![npm version](https://badge.fury.io/js/mutex-server.svg)](https://www.npmjs.com/package/mutex-server) 9 | [![Downloads](https://img.shields.io/npm/dm/mutex-server.svg)](https://www.npmjs.com/package/mutex-server) 10 | [![Build Status](https://github.com/samchon/mutex/workflows/build/badge.svg)](https://github.com/samchon/mutex/actions?query=workflow%3Abuild) 11 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fsamchon%2Fmutex-server.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fsamchon%2Fmutex-server?ref=badge_shield) 12 | 13 | Critical sections in the network level. 14 | 15 | The `mutex-server` is an npm module that can be used for building a mutex server. When you need to control a critical section on the entire system level, like distributed processing system using the network communications, this `mutex-server` can be a good solution. 16 | 17 | Installs and opens a `mutex-server` and let clients to connect to the server. The clients who're connecting to the `mutex-server` can utilize remote critical section components like [mutex](https://samchon.github.io/mutex/api/classes/msv.remotemutex.html) or [semaphore](https://samchon.github.io/mutex/api/classes/msv.remotesemaphore.html). 18 | 19 | Also, `mutex-server` has a safety device for network disconnections. When a client has been suddenly disconnected, all of the locks had acquired or tried by the client would be automatically unlocked or cancelled. Therefore, you don't worry about any network disconnection accident and just enjoy the `mutex-server` with confidence. 20 | 21 | 22 | 23 | 24 | ## 2. Features 25 | ### 2.1. Network Level 26 | When you want to open a `mutex-server`, utilize the [`MutexServer`](https://samchon.github.io/mutex/api/classes/msv.mutexserver.html) class and accept or reject connections from the remote clients by using the [`MutexAcceptor`](https://samchon.github.io/mutex/api/classes/msv.mutexacceptor.html) class. Otherwise, you want to connect to a remote `mutex-server` as a client, utilize the [`MutexConnector`](https://samchon.github.io/mutex/api/classes/msv.mutexconnector.html) class. 27 | 28 | - [`MutexServer`](https://samchon.github.io/mutex/api/classes/msv.mutexserver.html) 29 | - [`MutexAcceptor`](https://samchon.github.io/mutex/api/classes/msv.mutexacceptor.html) 30 | - [`MutexConnector`](https://samchon.github.io/mutex/api/classes/msv.mutexconnector.html) 31 | 32 | ### 2.2. Critical Section Components 33 | If you succeeded to connect to a `mutex-server`, as a client through the [`MutexConnector`](https://samchon.github.io/mutex/api/classes/msv.mutexconnector.html) class, you can utilize lots of remote critical section components like below. For reference, all of those critical section components are following the STL (Standard Template Library) design. 34 | 35 | Also, [`std.UniqueLock`](https://samchon.github.io/mutex/api/classes/std.uniquelock.html) and [`std.SharedLock`](https://samchon.github.io/mutex/api/classes/std.sharedlock.html) can be a good choice for safe development. They always ensure that acquired lock to be automatically unlocked, in any circumstance, even if an error occurs in your business code. 36 | 37 | - Solid Components 38 | - [`RemoteConditionVariable`](https://samchon.github.io/mutex/api/classes/msv.remoteconditionvariable.html) 39 | - [`RemoteMutex`](https://samchon.github.io/mutex/api/classes/msv.remotemutex.html) 40 | - [`RemoteSemaphore`](https://samchon.github.io/mutex/api/classes/msv.remotesemaphore.html) 41 | - Adaptor Components 42 | - [`RemoteBarrier`](https://samchon.github.io/mutex/api/classes/msv.remotebarrier.html) 43 | - [`RemoteLatch`](https://samchon.github.io/mutex/api/classes/msv.remotelatch.html) 44 | - Safety Helpers 45 | - [`std.UniqueLock`](https://samchon.github.io/mutex/api/classes/std.uniquelock.html) 46 | - [`std.SharedLock`](https://samchon.github.io/mutex/api/classes/std.sharedlock.html) 47 | 48 | 49 | 50 | 51 | ## 3. Usage 52 | ![mutex-server](https://user-images.githubusercontent.com/13158709/86332593-b285b200-bc85-11ea-8a2e-cbe30284d053.gif) 53 | 54 | Let's learn how to use the `mutex-server` through a sample project. I'll [open a server](https://samchon.github.io/mutex/api/classes/msv.mutexserver.html#open) and let 55 | 4 clients to [connect to the server](https://samchon.github.io/mutex/api/classes/msv.mutexconnector.html#connect). After those 4 clients' connections, they'll monopoly a critical section through [`RemoteMutex.lock()`](https://samchon.github.io/mutex/api/classes/msv.remotemutex.html#lock) method and start printing a line very slowly. 56 | 57 | After printing has been completed, each client will act one of them randomly: [unlock the mutex](https://samchon.github.io/mutex/api/classes/msv.remotemutex.html#unlock) or [close the connection](https://samchon.github.io/mutex/api/classes/msv.mutexconnector.html#close) without the unlock. As you know and as I've mentioned, if a client has been disconnected without returning locks that it had been acquired, the `mutex-server` will automatically release them. 58 | 59 | Therefore, two random actions would be confirmed to the same result: [`RemoteMutex.unlock()`](https://samchon.github.io/mutex/api/classes/msv.remotemutex.html#unlock). 60 | 61 | ```typescript 62 | import msv from "mutex-server"; 63 | import std from "tstl"; 64 | 65 | const PASSWORD = "qweqwe123!"; 66 | const PORT = 37119; 67 | 68 | async function client(index: number, character: string): Promise 69 | { 70 | // CONNECT TO THE SERVER 71 | let connector: msv.MutexConnector = new msv.MutexConnector(PASSWORD, null); 72 | await connector.connect(`ws://127.0.0.1:${PORT}`); 73 | 74 | // GET LOCK 75 | let mutex: msv.RemoteMutex = await connector.getMutex("printer"); 76 | await mutex.lock(); 77 | 78 | // PRINTS A LINE VERY SLOWLY MONOPOLYING THE MUTEX 79 | process.stdout.write(`Connector #${index} is monopolying a mutex: `); 80 | for (let i: number = 0; i < 20; ++i) 81 | { 82 | process.stdout.write(character); 83 | await std.sleep_for(50); 84 | } 85 | process.stdout.write("\n"); 86 | 87 | // ALTHOUGH THE CLIENT DOES NOT RELEASE THE LOCK 88 | if (Math.random() < 0.5) 89 | await mutex.unlock(); 90 | else // SERVER WILL UNLOCK IT AUTOMATICALLY AFTER THE DISCONNECTION 91 | await connector.close(); 92 | } 93 | 94 | async function main(): Promise 95 | { 96 | // OPEN SERVER 97 | let server: msv.MutexServer = new msv.MutexServer(); 98 | await server.open(PORT, async acceptor => 99 | { 100 | if (acceptor.header === PASSWORD) 101 | await acceptor.accept(null); 102 | else 103 | await acceptor.reject(); 104 | }); 105 | 106 | // CREATE 10 CLIENTS LOCKING MUTEX 107 | let promises: Promise[] = []; 108 | for (let i: number = 0; i < 4; ++i) 109 | { 110 | let character: string = std.randint(0, 9).toString(); 111 | promises.push( client(i + 1, character) ); 112 | } 113 | 114 | // WAIT THE CLIENTS TO BE DISCONNCTED AND CLOSE SERVER 115 | await Promise.all(promises); 116 | await server.close(); 117 | } 118 | main(); 119 | ``` 120 | 121 | 122 | 123 | 124 | ## 4. Appendix 125 | ### 4.1. Repositories 126 | `mutex-server` is an open source project following the [MIT license](https://github.com/samchon/mutex/blob/master/LICENSE). 127 | 128 | - Github: https://github.com/samchon/mutex 129 | - NPM: https://www.npmjs.com/package/mutex-server 130 | 131 | ### 4.2. Documents 132 | Someone who wants to know more about this `mutex-server`, I've prepared the [API documents](https://samchon.github.io/mutex/api). Through the [API documents](https://samchon.github.io/mutex/api), you can travel all of the [features](#2-features) defined in this `mutex-server`. 133 | 134 | Also, I'm planning to write the guide documents providing detailed learning course and lots of example projects handling network level critical sections. When the guide documents has been published, its URL address would be https://samchon.github.io/mutex and it would form like the [TGrid's](https://tgrid.com). 135 | 136 | - API Documents: https://samchon.github.io/mutex/api 137 | - Guide Documents: not yet, but soon. 138 | 139 | ### 4.3. Dependencies 140 | #### 4.3.1. [TypeScript](https://github.com/microsoft/typescript) 141 | I've developed this `mutex-server` with the [TypeScript](https://github.com/microsoft/typescript). 142 | 143 | Also, I hope all users of this `mutex-server` to use the [TypeScript](https://github.com/microsoft/typescript) too, for the safe development. As this `mutex-server` is designed to handling critical section in the network level, a tiny mistake like mis-typing can be a critical damage on the entire network system. 144 | 145 | It's the reason why I recommend you to use the [TypeScript](https://github.com/microsoft/typescript) when using this `mutex-server`. 146 | 147 | #### 4.3.2. [TSTL](https://github.com/samchon/tstl) 148 | This `mutex-server` is an extension module of the [TSTL](https://github.com/samchon/tstl). 149 | 150 | [TSTL](https://github.com/samchon/tstl) is an open source project migrating C++ STL (Standrad Template Library) to the TypeScript. Therefore, [TSTL](https://github.com/samchon/tstl) is following designs from the C++ standard committee and providing many modules like *containers*, *iterators*, *algorithms* and *functors* following the standard. 151 | 152 | However, TypeScript is not C++ and it's development environment is different with the C++, either. Therefore, there're some STL features that are not suitable for the TypeScript development. Also, there can be a feature not supported by STL, but it's an important one in the TypeScript development environment, too. 153 | 154 | To resolve such problems, [TSTL](https://github.com/samchon/tstl) is providing extension modules. This `mutex-server` is one of those extension module from the [TSTL](https://github.com/samchon/tstl), designed to support the network level critical sections: [tstl#74](https://github.com/samchon/tstl/issues/74). 155 | 156 | #### 4.3.3. [TGrid](https://github.com/samchon/tgrid) 157 | [TGrid](https://github.com/samchon/tgrid) is also an extension module of the [TSTL](https://github.com/samchon/tstl), designed to support the thread and network, too. 158 | 159 | The TGrid has implemented thread and network by inventing a new concept; [RFC](https://github.com/samchon/tgrid#13-remote-function-call) (Remote Function Call). Also, this `mutex-server` has realized its remote, network level critical section, components by utilizing the [RFC](https://github.com/samchon/tgrid#13-remote-function-call) concepts invented by the [TGrid](https://github.com/samchon/tgrid). -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mutex-server", 3 | "version": "0.6.1", 4 | "description": "Mutex Server using WebSocket", 5 | "main": "lib/index.js", 6 | "typings": "lib/index.d.ts", 7 | "exports": { 8 | ".": { 9 | "types": "./lib/index.d.ts", 10 | "require": "./lib/index.js", 11 | "import": "./lib/index.mjs" 12 | } 13 | }, 14 | "scripts": { 15 | "build": "npm run build:main && npm run build:test", 16 | "build:main": "rimraf lib && tsc && rollup -c", 17 | "build:test": "rimraf bin && tsc --project test/tsconfig.json", 18 | "dev": "npm run build:test -- --watch", 19 | "prepare": "ts-patch install", 20 | "prettier": "prettier src --write && prettier test --write", 21 | "test": "node bin/test" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/samchon/mutex" 26 | }, 27 | "keywords": [ 28 | "mutex", 29 | "semaphore", 30 | "condition", 31 | "variable", 32 | "latch", 33 | "barrier", 34 | "write", 35 | "read", 36 | "unique", 37 | "shared", 38 | "lock", 39 | "acquire", 40 | "server", 41 | "remote", 42 | "critical", 43 | "section", 44 | "thread", 45 | "tstl", 46 | "tgrid" 47 | ], 48 | "author": { 49 | "name": "Jeongho Nam", 50 | "email": "samchon.github@gmail.com", 51 | "url": "https://github.com/samchon" 52 | }, 53 | "license": "MIT", 54 | "bugs": { 55 | "url": "https://github.com/samchon/mutex/issues" 56 | }, 57 | "homepage": "https://github.com/samchon/mutex", 58 | "devDependencies": { 59 | "@rollup/plugin-terser": "^0.4.4", 60 | "@rollup/plugin-typescript": "^11.1.6", 61 | "@trivago/prettier-plugin-sort-imports": "^4.3.0", 62 | "@types/cli": "^0.11.19", 63 | "@types/node": "^13.11.1", 64 | "cli": "^1.0.1", 65 | "prettier": "^3.2.5", 66 | "rimraf": "^3.0.2", 67 | "rollup": "^4.13.1", 68 | "source-map-support": "^0.5.16", 69 | "ts-node": "^8.8.2", 70 | "ts-patch": "^3.1.2", 71 | "tslib": "^2.6.2", 72 | "typedoc": "^0.25.12", 73 | "typescript": "5.4.5", 74 | "typescript-transform-paths": "^3.4.7" 75 | }, 76 | "dependencies": { 77 | "tgrid": "^1.0.1", 78 | "tstl": "^3.0.0" 79 | }, 80 | "files": [ 81 | "LICENSE", 82 | "README.md", 83 | "lib", 84 | "src" 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "typescript", 3 | printWidth: 80, 4 | semi: true, 5 | tabWidth: 2, 6 | trailingComma: "all", 7 | importOrder: [ 8 | "", 9 | "^[./]", 10 | ], 11 | importOrderSeparation: true, 12 | importOrderSortSpecifiers: true, 13 | importOrderParserPlugins: ["decorators-legacy", "typescript"], 14 | }; 15 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const typescript = require("@rollup/plugin-typescript"); 2 | const terser = require("@rollup/plugin-terser"); 3 | 4 | module.exports = { 5 | input: "./src/index.ts", 6 | output: { 7 | dir: "lib", 8 | format: "esm", 9 | entryFileNames: "[name].mjs", 10 | sourcemap: true, 11 | }, 12 | plugins: [ 13 | typescript({ 14 | tsconfig: "tsconfig.json", 15 | module: "ES2020", 16 | target: "ES2020", 17 | }), 18 | terser({ 19 | format: { 20 | comments: "some", 21 | beautify: true, 22 | ecma: "2020", 23 | }, 24 | compress: false, 25 | mangle: false, 26 | module: true, 27 | }), 28 | ], 29 | }; 30 | -------------------------------------------------------------------------------- /src/MutexAcceptor.ts: -------------------------------------------------------------------------------- 1 | import { WebSocketAcceptor } from "tgrid"; 2 | 3 | import { ProviderGroup } from "./server/ProviderGroup"; 4 | 5 | /** 6 | * Acceptor of the `mutex-server`. 7 | * 8 | * - available only in NodeJS. 9 | * 10 | * The {@link MutexAcceptor} is a communicator class interacting with the remote client, through 11 | * websocket and [RPC](https://tgrid.com/docs/remote-procedure-call/) protocol, in the 12 | * `mutex-server`. The {@link MutexAcceptor} objects are always created by the {@link MutexServer} 13 | * class, whenever a remote client connects to the `mutex-server`. 14 | * 15 | * You can accept or reject the client's connection by calling {@link accept} or {@link reject} 16 | * method. If you {@link accept} the connection, the interaction would be started and the client 17 | * can access to critical component sections of this `mutex-server`. 18 | * 19 | * Also, when declaring this {@link MutexAcceptor} type, you've to define one template argument, 20 | * *Header*. The *Header* type repersents an initial data gotten from the remote client after 21 | * the connection. I hope you and client not to omit it and utilize it as an activation tool 22 | * to enhance security. 23 | * 24 | * @template Header Type of the header containing initial data. 25 | * @author Jeongho Nam - https://github.com/samchon 26 | */ 27 | export class MutexAcceptor
{ 28 | /** 29 | * @hidden 30 | */ 31 | private base_: WebSocketAcceptor; 32 | 33 | /** 34 | * @hidden 35 | */ 36 | private group_: ProviderGroup; 37 | 38 | /* ---------------------------------------------------------------- 39 | CONSTRUCTORS 40 | ---------------------------------------------------------------- */ 41 | /** 42 | * @hidden 43 | */ 44 | private constructor( 45 | base: WebSocketAcceptor, 46 | group: ProviderGroup, 47 | ) { 48 | this.base_ = base; 49 | this.group_ = group; 50 | } 51 | 52 | /** 53 | * @internal 54 | */ 55 | public static create
( 56 | base: WebSocketAcceptor, 57 | group: ProviderGroup, 58 | ): MutexAcceptor
{ 59 | return new MutexAcceptor(base, group); 60 | } 61 | 62 | /** 63 | * Close connection. 64 | * 65 | * Close connection with the remote client. 66 | * 67 | * When connection with the remote client has been closed, all of the locks the client had 68 | * acquired and tried would be automatically unlocked and cancelled by this `mutex-server`. 69 | * Also, remote critical section components had assigned to the client would be returned, too. 70 | * 71 | * @param code Closing code. 72 | * @param reason Reason why. 73 | * @throw DomainError when the connection is not online. 74 | */ 75 | public close(code?: number, reason?: string): Promise { 76 | return this.base_.close(code, reason); 77 | } 78 | 79 | /** 80 | * Join connection. 81 | * 82 | * Wait until connection with the server to be closed. 83 | */ 84 | public join(): Promise; 85 | 86 | /** 87 | * Join connection until timeout. 88 | * 89 | * Wait until connection with the server to be clsoed until timeout. 90 | * 91 | * @param ms The maximum miliseconds for waiting. 92 | * @return Whether succeeded to waiting in the given time. 93 | */ 94 | public join(ms: number): Promise; 95 | 96 | /** 97 | * Join connection until time expiration. 98 | * 99 | * Wait until connection with the server to be closed until time expiration. 100 | * 101 | * @param at The maximum time point to wait. 102 | * @return Whether succeeded to waiting in the given time. 103 | */ 104 | public join(at: Date): Promise; 105 | 106 | public join(param?: number | Date): Promise { 107 | return this.base_.join(param! as Date); 108 | } 109 | 110 | /* ---------------------------------------------------------------- 111 | ACCESSORS 112 | ---------------------------------------------------------------- */ 113 | /** 114 | * IP Address of client. 115 | */ 116 | public get ip(): string { 117 | return this.base_.ip; 118 | } 119 | 120 | /** 121 | * Path of client has connected. 122 | */ 123 | public get path(): string { 124 | return this.base_.path; 125 | } 126 | 127 | /** 128 | * Header containing initialization data like activation. 129 | */ 130 | public get header(): Header { 131 | return this.base_.header; 132 | } 133 | 134 | /** 135 | * Get state. 136 | * 137 | * Get current state of connection state with the remote client. List of values are such like 138 | * below: 139 | * 140 | * - `REJECTING`: The {@link MutexAcceptor.reject} method is on running. 141 | * - `NONE`: The {@link MutexAcceptor} instance is newly created, but did nothing yet. 142 | * - `ACCEPTING`: The {@link MutexAcceptor.accept} method is on running. 143 | * - `OPEN`: The connection is online. 144 | * - `CLOSING`: The {@link MutexAcceptor.close} method is on running. 145 | * - `CLOSED`: The connection is offline. 146 | */ 147 | public get state(): MutexAcceptor.State { 148 | return this.base_.state; 149 | } 150 | 151 | /* ---------------------------------------------------------------- 152 | HANDSHAKES 153 | ---------------------------------------------------------------- */ 154 | /** 155 | * Accept connection. 156 | * 157 | * Accepts, permits the client's connection to this `mutex-server` and starts interaction. 158 | * 159 | * @param provider An object providing additional features for the remote client. If you don't have plan to proivde any additional feature, assign `null`. 160 | */ 161 | public async accept(): Promise { 162 | await this.base_.accept(this.group_); 163 | this.base_.join().then(() => this.group_.destructor()); 164 | } 165 | 166 | /** 167 | * Reject connection. 168 | * 169 | * Reject without acceptance, any interaction. The connection would be closed immediately. 170 | * 171 | * @param code Closing code. 172 | * @param reason Reason why. 173 | */ 174 | public reject(status?: number, reason?: string): Promise { 175 | return this.base_.reject(status, reason); 176 | } 177 | } 178 | 179 | /** 180 | * 181 | */ 182 | export namespace MutexAcceptor { 183 | /** 184 | * Current state of the {@link MutexAcceptor} 185 | */ 186 | export import State = WebSocketAcceptor.State; 187 | } 188 | -------------------------------------------------------------------------------- /src/MutexConnector.ts: -------------------------------------------------------------------------------- 1 | import { WebSocketConnector } from "tgrid"; 2 | 3 | import { RemoteBarrier } from "./client/RemoteBarrier"; 4 | import { RemoteConditionVariable } from "./client/RemoteConditionVariable"; 5 | import { RemoteLatch } from "./client/RemoteLatch"; 6 | import { RemoteMutex } from "./client/RemoteMutex"; 7 | import { RemoteSemaphore } from "./client/RemoteSemaphore"; 8 | import { ProviderGroup } from "./server/ProviderGroup"; 9 | 10 | /** 11 | * Mutex server connector for client. 12 | * 13 | * The {@link MutexConnector} is a communicator class who can connect to a remote `mutex-server` 14 | * and interact with it through websocket and [RPC](https://tgrid.com/docs/remote-procedure-call/) 15 | * protocol. 16 | * 17 | * You can connect to the websocket `mutex-server` using {@link connect} method. After the 18 | * connection, get network level remote critical section components using one of below methods. 19 | * After utilizing those components, please do not forget to realising the component by calling 20 | * {@link IRemoteComponent.destructor} or closing connection with the `mutex-server` by calling 21 | * the {@link close} method to prevent waste of resources of the `mutex-server`. 22 | * 23 | * - Solid Components 24 | * - {@link getConditionVariable} 25 | * - {@link getMutex} 26 | * - {@link getSemaphore} 27 | * - Adaptor Comopnents 28 | * - {@link getBarrier} 29 | * - {@link getLatch} 30 | * 31 | * Also, when declaring this {@link MutexConnector} type, you've to define one template argument, 32 | * *Header*. The *Header* type repersents an initial data sending to the remote `mutex-server` 33 | * after connection. I hope you not to omit it and utilize it as an activation tool to 34 | * enhance security. 35 | * 36 | * @template Header Type of the *header* containing initial data. 37 | * @author Jeongho Nam - https://github.com/samchon 38 | */ 39 | export class MutexConnector
{ 40 | /** 41 | * @hidden 42 | */ 43 | private connector_: WebSocketConnector; 44 | 45 | /* ----------------------------------------------------------- 46 | CONSTRUCTORS 47 | ----------------------------------------------------------- */ 48 | /** 49 | * Initializer Constructor. 50 | * 51 | * @param header Initial data sending to the remote server after connection. Hope to use it as an activation tool. 52 | */ 53 | public constructor(header: Header) { 54 | this.connector_ = new WebSocketConnector(header, null); 55 | } 56 | 57 | /** 58 | * Connect to a remote `mutex-server`. 59 | * 60 | * Try connection to the remote `mutex-server` with its *address* and waiting for the server 61 | * to accept the trial. If the `mutex-server` accepts your connection, the function would be 62 | * returned. Otherwise, the server rejects your connection, an exception would be thrown. 63 | * 64 | * After the connection and your business (using remote critical sections) has been 65 | * completed, please don't forget closing the connection in time to prevent waste of the 66 | * server resource. 67 | * 68 | * @param url URL address to connect. 69 | * @throw DomainError when connection is not offline . 70 | * @throw WebError when target server is not following adequate protocol. 71 | * @throw WebError when server rejects your connection. 72 | * @throw WebError when server does not accept your connection until timeout. 73 | */ 74 | public connect(url: string, timeout?: number): Promise { 75 | return this.connector_.connect(url, { timeout }); 76 | } 77 | 78 | /** 79 | * Close connection. 80 | * 81 | * Close connection from the remote `mutex-server`. 82 | * 83 | * When connection with the `mutex-server` has been closed, all of the locks you had acquired 84 | * and tried would be automatically unlocked and cancelled by the server. Therefore, if you've 85 | * tried to get locks through the remote critical section components by calling their methods, 86 | * those methods would throw {@link RuntimeError} exceptions. 87 | * 88 | * @throw DomainError when the connection is not online. 89 | */ 90 | public async close(): Promise { 91 | await this.connector_.close(); 92 | } 93 | 94 | /** 95 | * Join connection. 96 | * 97 | * Wait until connection with the server to be closed. 98 | */ 99 | public join(): Promise; 100 | 101 | /** 102 | * Join connection until timeout. 103 | * 104 | * Wait until connection with the server to be clsoed until timeout. 105 | * 106 | * @param ms The maximum miliseconds for waiting. 107 | * @return Whether succeeded to waiting in the given time. 108 | */ 109 | public join(ms: number): Promise; 110 | 111 | /** 112 | * Join connection until time expiration. 113 | * 114 | * Wait until connection with the server to be closed until time expiration. 115 | * 116 | * @param at The maximum time point to wait. 117 | * @return Whether succeeded to waiting in the given time. 118 | */ 119 | public join(at: Date): Promise; 120 | 121 | public join(param?: number | Date): Promise { 122 | return this.connector_.join(param! as Date); 123 | } 124 | 125 | /* ----------------------------------------------------------- 126 | ACCESSORS 127 | ----------------------------------------------------------- */ 128 | /** 129 | * Get connection url. 130 | */ 131 | public get url(): string | undefined { 132 | return this.connector_.url; 133 | } 134 | 135 | /** 136 | * Get state. 137 | * 138 | * Get current state of connection state with the `mutex-server`. List of values are such like 139 | * below: 140 | * 141 | * - `NONE`: The {@link MutexConnector} instance is newly created, but did nothing yet. 142 | * - `CONNECTING`: The {@link MutexConnector.connect} method is on running. 143 | * - `OPEN`: The connection is online. 144 | * - `CLOSING`: The {@link MutexConnector.close} method is on running. 145 | * - `CLOSED`: The connection is offline. 146 | */ 147 | public get state(): MutexConnector.State { 148 | return this.connector_.state; 149 | } 150 | 151 | /** 152 | * Get header. 153 | */ 154 | public get header(): Header { 155 | return this.connector_.header; 156 | } 157 | 158 | /* ----------------------------------------------------------- 159 | THREAD COMPONENTS 160 | ----------------------------------------------------------- */ 161 | /** 162 | * Get remote condition variable. 163 | * 164 | * Get remote condition variable from the `mutex-server`. 165 | * 166 | * If the `mutex-server` doesn't have the *key* named condition variable, the server will 167 | * create a new condition variable instance. Otherwise, the server already has the *key* named 168 | * condition variable, server will return it directly. 169 | * 170 | * @param key An identifier name to be created or search for. 171 | * @return A {@link RemoteConditionVariable} object. 172 | */ 173 | public getConditionVariable(key: string): Promise { 174 | return RemoteConditionVariable.create( 175 | this.connector_.getDriver().condition_variables, 176 | key, 177 | ); 178 | } 179 | 180 | /** 181 | * Get remote mutex. 182 | * 183 | * Get remote mutex from the `mutex-server`. 184 | * 185 | * If the `mutex-server` doesn't have the *key* named mutex, the server will create a new 186 | * mutex instance. Otherwise, the server already has the *key* named mutex, server will return 187 | * it directly. 188 | * 189 | * @param key An identifier name to be created or search for. 190 | * @return A {@link RemoteMutex} object. 191 | */ 192 | public getMutex(key: string): Promise { 193 | return RemoteMutex.create(this.connector_.getDriver().mutexes, key); 194 | } 195 | 196 | /** 197 | * Get remote semaphore. 198 | * 199 | * Get remote semaphore from the `mutex-server`. 200 | * 201 | * If the `mutex-server` doesn't have the *key* named semaphore, the server will create a new 202 | * semaphore instance with your *count*. Otherwise, the server already has the *key* named 203 | * semaphore, server will return it directly. 204 | * 205 | * Therefore, if the server already has the *key* named semaphore, its 206 | * {@link RemoteSemaphore.max} can be different with your *count*. 207 | * 208 | * @param key An identifier name to be created or search for. 209 | * @param count Downward counter of the target semaphore, if newly created. 210 | * @return A {@link RemoteSemaphore} object. 211 | */ 212 | public getSemaphore(key: string, count: number): Promise { 213 | return RemoteSemaphore.create( 214 | this.connector_.getDriver().semaphores, 215 | key, 216 | count, 217 | ); 218 | } 219 | 220 | /** 221 | * Get remote barrier. 222 | * 223 | * Get remote barrier from the `mutex-server`. 224 | * 225 | * If the `mutex-server` doesn't have the *key* named barrier, the server will create a new 226 | * barrier instance with your *count*. Otherwise, the server already has the *key* named 227 | * barrier, server will return it directly. 228 | * 229 | * Therefore, if the server already has the *key* named barrier, its downward counter can be 230 | * different with your *count*. 231 | * 232 | * @param key An identifier name to be created or search for. 233 | * @param count Downward counter of the target barrier, if newly created. 234 | * @return A {@link RemoteBarrier} object. 235 | */ 236 | public getBarrier(key: string, count: number): Promise { 237 | return RemoteBarrier.create( 238 | this.connector_.getDriver().barriers, 239 | key, 240 | count, 241 | ); 242 | } 243 | 244 | /** 245 | * Get remote latch. 246 | * 247 | * Get remote latch from the `mutex-server`. 248 | * 249 | * If the `mutex-server` doesn't have the *key* named latch, the server will create a new 250 | * latch instance with your *count*. Otherwise, the server already has the *key* named latch, 251 | * server will return it directly. 252 | * 253 | * Therefore, if the server already has the *key* named latch, its downward counter can be 254 | * different with your *count*. 255 | * 256 | * @param identifier An identifier name to be created or search for. 257 | * @param count Downward counter of the target latch, if newly created. 258 | * @return A {@link RemoteLatch} object. 259 | */ 260 | public getLatch(identifier: string, count: number): Promise { 261 | return RemoteLatch.create( 262 | this.connector_.getDriver().latches, 263 | identifier, 264 | count, 265 | ); 266 | } 267 | } 268 | 269 | /** 270 | * 271 | */ 272 | export namespace MutexConnector { 273 | /** 274 | * Current state of the {@link MutexConnector} 275 | */ 276 | export import State = WebSocketConnector.State; 277 | } 278 | -------------------------------------------------------------------------------- /src/MutexServer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @packageDocumentation 3 | * @module msv 4 | */ 5 | //----------------------------------------------------------- 6 | import { WebSocketServer } from "tgrid"; 7 | import { MutexAcceptor } from "./MutexAcceptor"; 8 | 9 | import { ProviderGroup } from "./server/ProviderGroup"; 10 | import { GlobalGroup } from "./server/GlobalGroup"; 11 | 12 | /** 13 | * The Mutex Server. 14 | * 15 | * - available only in NodeJS. 16 | * 17 | * The {@link MutexServer} is a class who can open an `mutex-server`. Clients connecting to the 18 | * `mutex-server` would communicate with this server through {@link MutexAcceptor} objects using 19 | * websocket and [RPC](https://tgrid.com/docs/remote-procedure-call/) protocol. 20 | * 21 | * To open the `mutex-server`, call the {@link open} method with your custom callback function 22 | * which would be called whenever a {@link MutexAcceptor} has been newly created by a client's 23 | * connection. 24 | * 25 | * Also, when declaring this {@link MutexServer} type, you've to define one template argument, 26 | * *Header*. The *Header* type repersents an initial data gotten from the remote client after 27 | * the connection. I hope you and client not to omit it and utilize it as an activation tool 28 | * to enhance security. 29 | * 30 | * @template Header Type of the *header* containing initial data. 31 | * @author Jeongho Nam - https://github.com/samchon 32 | */ 33 | export class MutexServer
{ 34 | /** 35 | * @hidden 36 | */ 37 | private server_: WebSocketServer; 38 | 39 | /** 40 | * @hidden 41 | */ 42 | private components_: GlobalGroup; 43 | 44 | /* ----------------------------------------------------------- 45 | CONSTRUCTORS 46 | ----------------------------------------------------------- */ 47 | /** 48 | * Default Constructor. 49 | * 50 | * Create a web-socket server, without secured option (`ws://`). 51 | */ 52 | public constructor(); 53 | 54 | /** 55 | * Initializer Constructor. 56 | * 57 | * Create a secured web-socket server with *key* and *certification* data (`wss://`). 58 | * 59 | * @param key Key string. 60 | * @param cert Certification string. 61 | */ 62 | public constructor(key: string, cert: string); 63 | 64 | public constructor(key?: string, cert?: string) { 65 | this.server_ = new WebSocketServer(key!, cert!); 66 | this.components_ = new GlobalGroup(); 67 | } 68 | 69 | /** 70 | * Open a `mutex-server`. 71 | * 72 | * Open a `mutex-server` through web-socket protocol, with its *port* number and *handler* 73 | * function determining whether to accept the client's connection or not. After the 74 | * `mutex-server` has been opened, clients can connect to that `mutex-server` by using the 75 | * {@link MutexConnector} class. 76 | * 77 | * When implementing the *handler* function with the {@link MutexAcceptor} instance, calls the 78 | * {@link MutexAcceptor.accept} method if you want to accept the new client's connection. 79 | * Otherwise you dont't want to accept the client and reject its connection, just calls the 80 | * {@link MutexAcceptor.reject} instead. 81 | * 82 | * Note that, this `mutex-server` handles the global critical sections on the entire network 83 | * level. If you define the *predicator* function to accept every client's connection, some 84 | * bad guy can spoil your own `mutex-server` by monopolying those critical sections. 85 | * Therefore, don't implement the *handler* function to call only {@link MutexAcceptor.accept} 86 | * method, but filter their connections by considering their {@link MutexAcceptor.header} 87 | * data. 88 | * 89 | * @param port Port number to listen. 90 | * @param handler A handler function determining whether to accept the client's connection or not. 91 | */ 92 | public open( 93 | port: number, 94 | handler: (acceptor: MutexAcceptor
) => Promise, 95 | ): Promise { 96 | return this.server_.open(port, async (base) => { 97 | const group: ProviderGroup = new ProviderGroup(this.components_, base); 98 | const acceptor: MutexAcceptor
= MutexAcceptor.create(base, group); 99 | await handler(acceptor); 100 | }); 101 | } 102 | 103 | /** 104 | * Close the `mutex-server`. 105 | * 106 | * Close the `mutex-server` and disconnect all the remote connections between its clients. 107 | * Therefore, clients who are using the {@link MutexConnector} class, they can't use the 108 | * remote critical section components more. 109 | * 110 | * Also, closing the server means that all of the [RPC](https://tgrid.com/docs/remote-procedure-call/)s 111 | * between the server and had connected clients would be destroied. Therefore, all of the [RPC](https://tgrid.com/docs/remote-procedure-call/)s 112 | * that are not returned (completed) yet would ge {@link RuntimeError} exception. 113 | */ 114 | public close(): Promise { 115 | return this.server_.close(); 116 | } 117 | 118 | /* ----------------------------------------------------------- 119 | ACCESSORS 120 | ----------------------------------------------------------- */ 121 | /** 122 | * Get server state. 123 | * 124 | * Get current state of the `mutex-server`. 125 | * 126 | * List of values are such like below: 127 | * 128 | * - `NONE`: The `{@link MutexServer} instance is newly created, but did nothing yet. 129 | * - `OPENING`: The {@link MutexServer.open} method is on running. 130 | * - `OPEN`: The `mutex-server` is online. 131 | * - `CLOSING`: The {@link MutexServer.close} method is on running. 132 | * - `CLOSED`: The `mutex-server` is offline. 133 | */ 134 | public get state(): MutexServer.State { 135 | return this.server_.state; 136 | } 137 | } 138 | 139 | /** 140 | * 141 | */ 142 | export namespace MutexServer { 143 | /** 144 | * Current state of the {@link MutexServer} 145 | */ 146 | export import State = WebSocketServer.State; 147 | } 148 | -------------------------------------------------------------------------------- /src/client/RemoteBarrier.ts: -------------------------------------------------------------------------------- 1 | import { Promisive } from "tgrid"; 2 | 3 | import { BarriersProvider } from "../server/providers/BarriersProvider"; 4 | 5 | /** 6 | * Remote Barrier. 7 | * 8 | * The `RemoteBarrier` class blocks critical sections until the downward counter to be zero. 9 | * Unlike the {@link RemoteLatch} class whose downward counter is disposable, `RemoteBarrier` 10 | * can re-use the downward counter repeatedly, resetting counter to be initial value whenever 11 | * reach to the zero. 12 | * 13 | * @author Jeongho Nam - https://github.com/samchon 14 | */ 15 | export class RemoteBarrier { 16 | /** 17 | * @hidden 18 | */ 19 | private controller_: Promisive; 20 | 21 | /** 22 | * @hidden 23 | */ 24 | private name_: string; 25 | 26 | /* ----------------------------------------------------------- 27 | CONSTRUCTORS 28 | ----------------------------------------------------------- */ 29 | /** 30 | * @hidden 31 | */ 32 | private constructor(controller: Promisive, name: string) { 33 | this.controller_ = controller; 34 | this.name_ = name; 35 | } 36 | 37 | /** 38 | * @internal 39 | */ 40 | public static async create( 41 | controller: Promisive, 42 | name: string, 43 | count: number, 44 | ): Promise { 45 | await controller.emplace(name, count); 46 | return new RemoteBarrier(controller, name); 47 | } 48 | 49 | /* ----------------------------------------------------------- 50 | WAIT FUNCTIONS 51 | ----------------------------------------------------------- */ 52 | /** 53 | * Waits until the counter to be zero. 54 | * 55 | * Blocks the function calling until internal counter to be reached to the zero. 56 | */ 57 | public wait(): Promise { 58 | return this.controller_.wait(this.name_); 59 | } 60 | 61 | /** 62 | * Tries to wait until the counter to be zero in timeout. 63 | * 64 | * Attempts to block the function calling until internal counter to be reached to the zero 65 | * in timeout. If succeeded to waiting the counter to be reached to the zero, it returns 66 | * `true`. Otherwise, the {@link RemoteBarrier} fails to reach to the zero in the given time, 67 | * the function gives up the waiting and returns `false`. 68 | * 69 | * @param ms The maximum miliseconds for waiting. 70 | * @return Whether succeeded to waiting in the given time. 71 | */ 72 | public wait_for(ms: number): Promise { 73 | return this.controller_.wait_for(this.name_, ms); 74 | } 75 | 76 | /** 77 | * Tries to wait until the counter to be zero in time expiration. 78 | * 79 | * Attempts to block the function calling until internal counter to be reached to the zero 80 | * in time expiration. If succeeded to waiting the counter to be reached to the zero, it 81 | * returns `true`. Otherwise, the {@link RemoteBarrier} fails to reach to the zero in the 82 | * given time, the function gives up the waiting and returns `false`. 83 | * 84 | * @param at The maximum time point to wait. 85 | * @return Whether succeeded to waiting in the given time. 86 | */ 87 | public async wait_until(at: Date): Promise { 88 | const ms: number = at.getTime() - Date.now(); 89 | return await this.wait_for(ms); 90 | } 91 | 92 | /* ----------------------------------------------------------- 93 | ARRIVAL FUNCTIONS 94 | ----------------------------------------------------------- */ 95 | /** 96 | * Derecements the counter. 97 | * 98 | * Decrements the counter by *n* without blocking. 99 | * 100 | * If the parametric value *n* is equal to or greater than internal counter, so that the 101 | * internal counter be equal to or less than zero, everyone who are {@link wait waiting} for 102 | * the {@link Latch} would continue their executions. 103 | * 104 | * @param n Value of the decrement. Default is 1. 105 | */ 106 | public arrive(n: number = 1): Promise { 107 | return this.controller_.arrive(this.name_, n); 108 | } 109 | 110 | /** 111 | * Decrements the counter and waits until the counter to be zero. 112 | * 113 | * Decrements the counter by one and blocks the section until internal counter to be zero. 114 | * 115 | * If the the remained counter be zero by this decrement, everyone who are 116 | * {@link wait waiting} for the {@link RemoteBarrier} would continue their executions 117 | * including this one. 118 | */ 119 | public arrive_and_drop(): Promise { 120 | return this.controller_.arrive_and_drop(this.name_); 121 | } 122 | 123 | /** 124 | * Decrements the counter and initial size at the same time. 125 | * 126 | * Decrements not only internal counter, but also initialize size of the counter at the same 127 | * time. If the remained counter be zero by the decrement, everyone who are 128 | * {@link wait waiting} for the {@link RemoteBarrier} would continue their executions. 129 | */ 130 | public arrive_and_wait(): Promise { 131 | return this.controller_.arrive_and_wait(this.name_); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/client/RemoteConditionVariable.ts: -------------------------------------------------------------------------------- 1 | import { Promisive } from "tgrid"; 2 | 3 | import { ConditionVariablesProvider } from "../server/providers/ConditionVariablesProvider"; 4 | 5 | /** 6 | * Remote ConditionVariable. 7 | * 8 | * The `RemoteConditionVariable` class blocks critical sections until be notified. 9 | * 10 | * @author Jeongho Nam - https://github.com/samchon 11 | */ 12 | export class RemoteConditionVariable { 13 | /** 14 | * @hidden 15 | */ 16 | private controller_: Promisive; 17 | 18 | /** 19 | * @hidden 20 | */ 21 | private name_: string; 22 | 23 | /* ----------------------------------------------------------- 24 | CONSTRUCTORS 25 | ----------------------------------------------------------- */ 26 | /** 27 | * @hidden 28 | */ 29 | private constructor( 30 | controller: Promisive, 31 | name: string, 32 | ) { 33 | this.controller_ = controller; 34 | this.name_ = name; 35 | } 36 | 37 | /** 38 | * @internal 39 | */ 40 | public static async create( 41 | controller: Promisive, 42 | name: string, 43 | ): Promise { 44 | await controller.emplace(name, undefined); 45 | return new RemoteConditionVariable(controller, name); 46 | } 47 | 48 | /* ----------------------------------------------------------- 49 | WAIT FUNCTIONS 50 | ----------------------------------------------------------- */ 51 | /** 52 | * Wait until notified. 53 | */ 54 | public wait(): Promise; 55 | 56 | /** 57 | * Wait until predicator returns true. 58 | * 59 | * This method is equivalent to: 60 | * 61 | ```typescript 62 | while (!await predicator()) 63 | await this.wait(); 64 | ``` 65 | * 66 | * @param predicator A predicator function determines completion. 67 | */ 68 | public wait(predicator: RemoteConditionVariable.Predicator): Promise; 69 | 70 | public async wait( 71 | predicator?: RemoteConditionVariable.Predicator, 72 | ): Promise { 73 | if (!predicator) return await this._Wait(); 74 | 75 | while (!(await predicator())) await this._Wait(); 76 | } 77 | 78 | /** 79 | * Wait for timeout or until notified. 80 | * 81 | * @param ms The maximum miliseconds for waiting. 82 | * @return Whether awaken by notification or timeout. 83 | */ 84 | public wait_for(ms: number): Promise; 85 | 86 | /** 87 | * Wait until timeout or predicator returns true. 88 | * 89 | * This method is equivalent to: 90 | ```typescript 91 | const at: Date = new Date(Date.now() + ms); 92 | while (!await predicator()) { 93 | if (!await this.wait_until(at)) 94 | return await predicator(); 95 | } 96 | return true; 97 | ``` 98 | * 99 | * @param ms The maximum miliseconds for waiting. 100 | * @param predicator A predicator function determines the completion. 101 | * @return Returned value of the *predicator*. 102 | */ 103 | public wait_for( 104 | ms: number, 105 | predicator: RemoteConditionVariable.Predicator, 106 | ): Promise; 107 | 108 | public async wait_for( 109 | ms: number, 110 | predicator?: RemoteConditionVariable.Predicator, 111 | ): Promise { 112 | const at: Date = new Date(Date.now() + ms); 113 | return this.wait_until(at, predicator!); 114 | } 115 | 116 | /** 117 | * Wait until notified or time expiration. 118 | * 119 | * @param at The maximum time point to wait. 120 | * @return Whether awaken by notification or time expiration. 121 | */ 122 | public wait_until(at: Date): Promise; 123 | 124 | /** 125 | * Wait until time expiration or predicator returns true. 126 | * 127 | * This method is equivalent to: 128 | ```typescript 129 | while (!await predicator()) { 130 | if (!await this.wait_until(at)) 131 | return await predicator(); 132 | } 133 | return true; 134 | ``` 135 | * 136 | * @param at The maximum time point to wait. 137 | * @param predicator A predicator function determines the completion. 138 | * @return Returned value of the *predicator*. 139 | */ 140 | public wait_until( 141 | at: Date, 142 | predicator: RemoteConditionVariable.Predicator, 143 | ): Promise; 144 | 145 | public async wait_until( 146 | at: Date, 147 | predicator?: RemoteConditionVariable.Predicator, 148 | ): Promise { 149 | if (!predicator) return await this._Wait_until(at); 150 | 151 | while (!(await predicator())) 152 | if (!(await this._Wait_until(at))) return await predicator(); 153 | 154 | return true; 155 | } 156 | 157 | /** 158 | * @hidden 159 | */ 160 | private _Wait(): Promise { 161 | return this.controller_.wait(this.name_); 162 | } 163 | 164 | /** 165 | * @hidden 166 | */ 167 | private async _Wait_until(at: Date): Promise { 168 | const ms: number = at.getTime() - Date.now(); 169 | return await this.controller_.wait_for(this.name_, ms); 170 | } 171 | 172 | /* ----------------------------------------------------------- 173 | NOTIFIERS 174 | ----------------------------------------------------------- */ 175 | /** 176 | * Notify, wake only one up. 177 | */ 178 | public notify_one(): Promise { 179 | return this.controller_.notify_one(this.name_); 180 | } 181 | 182 | /** 183 | * Notify, wake all up. 184 | */ 185 | public notify_all(): Promise { 186 | return this.controller_.notify_all(this.name_); 187 | } 188 | } 189 | 190 | /** 191 | * 192 | */ 193 | export namespace RemoteConditionVariable { 194 | /** 195 | * Type of predicator function who determines the completion. 196 | */ 197 | export type Predicator = () => boolean | Promise; 198 | } 199 | -------------------------------------------------------------------------------- /src/client/RemoteLatch.ts: -------------------------------------------------------------------------------- 1 | import { Promisive } from "tgrid"; 2 | 3 | import { LatchesProvider } from "../server/providers/LatchesProvider"; 4 | 5 | /** 6 | * Remote Latch. 7 | * 8 | * The `RemoteLatch` class blocks critical sections until the downward counter to be zero. 9 | * Howver, unlike {@link RemoteBarrier} who can reusable that downward counter be reset whenever 10 | * reach to the zero, downward of the `RemoteLatch` is not reusable but diposable. 11 | * 12 | * @author Jeongho Nam - https://github.com/samchon 13 | */ 14 | export class RemoteLatch { 15 | /** 16 | * @hidden 17 | */ 18 | private controller_: Promisive; 19 | 20 | /** 21 | * @hidden 22 | */ 23 | private name_: string; 24 | 25 | /* ----------------------------------------------------------- 26 | CONSTRUCTORS 27 | ----------------------------------------------------------- */ 28 | /** 29 | * @hidden 30 | */ 31 | private constructor(controller: Promisive, name: string) { 32 | this.controller_ = controller; 33 | this.name_ = name; 34 | } 35 | 36 | /** 37 | * @internal 38 | */ 39 | public static async create( 40 | controller: Promisive, 41 | name: string, 42 | count: number, 43 | ): Promise { 44 | await controller.emplace(name, count); 45 | return new RemoteLatch(controller, name); 46 | } 47 | 48 | /* ----------------------------------------------------------- 49 | WAIT FUNCTIONS 50 | ----------------------------------------------------------- */ 51 | /** 52 | * Waits until the counter to be zero. 53 | * 54 | * Blocks the function calling until internal counter to be reached to the zero. 55 | * 56 | * If the {@link RemoteLatch} already has been reached to the zero, it would be returned 57 | * immediately. 58 | */ 59 | public wait(): Promise { 60 | return this.controller_.wait(this.name_); 61 | } 62 | 63 | /** 64 | * Test whether the counter has been reached to the zero. 65 | * 66 | * The {@link try_wait} function tests whether the internal counter has been reached to the 67 | * zero. 68 | * 69 | * @return Whether reached to zero or not. 70 | */ 71 | public try_wait(): Promise { 72 | return this.controller_.try_wait(this.name_); 73 | } 74 | 75 | /** 76 | * Tries to wait until the counter to be zero in timeout. 77 | * 78 | * Attempts to block the function calling until internal counter to be reached to the zero 79 | * in timeout. If succeeded to waiting the counter to be reached to the zero, it returns 80 | * `true`. Otherwise, the {@link RemoteLatch} fails to reach to the zero in the given time, 81 | * the function gives up the waiting and returns `false`. 82 | * 83 | * If the {@link RemoteLatch} already has been reached to the zero, it would return `true` 84 | * directly. 85 | * 86 | * @param ms The maximum miliseconds for waiting. 87 | * @return Whether succeeded to waiting in the given time. 88 | */ 89 | public wait_for(ms: number): Promise { 90 | return this.controller_.wait_for(this.name_, ms); 91 | } 92 | 93 | /** 94 | * Tries to wait until the counter to be zero in time expiration. 95 | * 96 | * Attempts to block the function calling until internal counter to be reached to the zero 97 | * in time expiration. If succeeded to waiting the counter to be reached to the zero, it 98 | * returns `true`. Otherwise, the {@link RemoteLatch} fails to reach to the zero in the 99 | * given time, the function gives up the waiting and returns `false`. 100 | * 101 | * If the {@link RemoteLatch} already has been reached to the zero, it would return `true` 102 | * directly. 103 | * 104 | * @param at The maximum time point to wait. 105 | * @return Whether succeeded to waiting in the given time. 106 | */ 107 | public async wait_until(at: Date): Promise { 108 | const ms: number = at.getTime() - Date.now(); 109 | return await this.wait_for(ms); 110 | } 111 | 112 | /* ----------------------------------------------------------- 113 | ARRIVALS 114 | ----------------------------------------------------------- */ 115 | /** 116 | * Derecements the counter. 117 | * 118 | * Decrements the counter by *n* without blocking. 119 | * 120 | * If the parametric value *n* is equal to or greater than internal counter, so that the 121 | * internal counter be equal to or less than zero, everyone who are {@link wait waiting} for 122 | * the {@link RemoteLatch} would continue their execution. 123 | * 124 | * @param n Value of the decrement. Default is 1. 125 | */ 126 | public count_down(n: number = 1): Promise { 127 | return this.controller_.count_down(this.name_, n); 128 | } 129 | 130 | /** 131 | * Decrements the counter and waits until the counter to be zero. 132 | * 133 | * Decrements the counter by *n* and blocks the section until internal counter to be zero. 134 | * 135 | * If the parametric value *n* is equal to or greater than internal counter, so that the 136 | * internal counter be equal to or less than zero, everyone who are {@link wait waiting} for 137 | * the {@link RemoteLatch} would continue their execution including this one. 138 | * 139 | * @param n Value of the decrement. Default is 1. 140 | */ 141 | public arrive_and_wait(): Promise { 142 | return this.controller_.arrive_and_wait(this.name_); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/client/RemoteMutex.ts: -------------------------------------------------------------------------------- 1 | import { Promisive } from "tgrid"; 2 | 3 | import { MutexesProvider } from "../server/providers/MutexesProvider"; 4 | 5 | /** 6 | * Remote Mutex. 7 | * 8 | * @author Jeongho Nam - https://github.com/samchon 9 | */ 10 | export class RemoteMutex { 11 | /** 12 | * @hidden 13 | */ 14 | private controller_: Promisive; 15 | 16 | /** 17 | * @hidden 18 | */ 19 | private name_: string; 20 | 21 | /* ----------------------------------------------------------- 22 | CONSTRUCTORS 23 | ----------------------------------------------------------- */ 24 | /** 25 | * @hidden 26 | */ 27 | private constructor(controller: Promisive, name: string) { 28 | this.controller_ = controller; 29 | this.name_ = name; 30 | } 31 | 32 | /** 33 | * @internal 34 | */ 35 | public static async create( 36 | controller: Promisive, 37 | name: string, 38 | ): Promise { 39 | await controller.emplace(name, undefined); 40 | return new RemoteMutex(controller, name); 41 | } 42 | 43 | /* ----------------------------------------------------------- 44 | WRITE LOCK 45 | ----------------------------------------------------------- */ 46 | /** 47 | * Write locks the mutex. 48 | * 49 | * Monopolies a mutex until be {@link unlock unlocked}. If there're someone who have already 50 | * {@link lock monopolied} or {@link lock_shared shared} the mutex, the function call would 51 | * be blocked until all of them to return their acquistions by calling {@link unlock} or 52 | * {@link unlock_shared} methods. 53 | * 54 | * In same reason, if you don't call the {@link unlock} function after your business, the 55 | * others who want to {@link lock monopoly} or {@link lock_shared share} the mutex would be 56 | * fall into the forever sleep. Therefore, never forget to calling the {@link unlock} function 57 | * or utilize the {@link UniqueLock.lock} function instead to ensure the safety. 58 | */ 59 | public lock(): Promise { 60 | return this.controller_.lock(this.name_); 61 | } 62 | 63 | /** 64 | * Tries to write lock the mutex. 65 | * 66 | * Attempts to monopoly a mutex without blocking. If succeeded to monopoly the mutex 67 | * immediately, it returns `true` directly. Otherwise there's someone who has already 68 | * {@link lock monopolied} or {@link lock_shared shared} the mutex, the function gives up the 69 | * trial immediately and returns `false` directly. 70 | * 71 | * Note that, if you succeeded to monopoly the mutex (returns `true`) but do not call the 72 | * {@link unlock} function after your business, the others who want to {@link lock monopoly} 73 | * or {@link lock_shared share} the mutex would be fall into the forever sleep. Therefore, 74 | * never forget to calling the {@link unlock} function or utilize the 75 | * {@link UniqueLock.try_lock} function instead to ensure the safety. 76 | * 77 | * @return Whether succeeded to monopoly the mutex or not. 78 | */ 79 | public try_lock(): Promise { 80 | return this.controller_.try_lock(this.name_); 81 | } 82 | 83 | /** 84 | * Tries to write lock the mutex until timeout. 85 | * 86 | * Attempts to monopoly a mutex until timeout. If succeeded to monopoly the mutex until the 87 | * timeout, it returns `true`. Otherwise failed to acquiring the lock in the given time, the 88 | * function gives up the trial and returns `false`. 89 | * 90 | * Failed to acquiring the lock in the given time (returns `false`), it means that there's 91 | * someone who has already {@link lock monopolied} or {@link lock_shared shared} the mutex and 92 | * does not return it over the timeout. 93 | * 94 | * Note that, if you succeeded to monopoly the mutex (returns `true`) but do not call the 95 | * {@link unlock} function after your business, the others who want to {@link lock monopoly} 96 | * or {@link lock_shared share} the mutex would be fall into the forever sleep. Therefore, 97 | * never forget to calling the {@link unlock} function or utilize the 98 | * {@link UniqueLock.try_lock_for} function instead to ensure the safety. 99 | * 100 | * @param ms The maximum miliseconds for waiting. 101 | * @return Whether succeeded to monopoly the mutex or not. 102 | */ 103 | public try_lock_for(ms: number): Promise { 104 | return this.controller_.try_lock_for(this.name_, ms); 105 | } 106 | 107 | /** 108 | * Tries to write lock the mutex until time expiration. 109 | * 110 | * Attemps to monopoly a mutex until time expiration. If succeeded to monopoly the mutex 111 | * until the time expiration, it returns `true`. Otherwise failed to acquiring the lock in the 112 | * given time, the function gives up the trial and returns `false`. 113 | * 114 | * Failed to acquiring the lock in the given time (returns `false`), it means that there's 115 | * someone who has already {@link lock monopolied} or {@link lock_shared shared} the mutex and 116 | * does not return it over the time expiration. 117 | * 118 | * Note that, if you succeeded to monopoly the mutex (returns `true`) but do not call the 119 | * {@link unlock} function after your business, the others who want to {@link lock monopoly} 120 | * or {@link lock_shared share} the mutex would be fall into the forever sleep. Therefore, 121 | * never forget to calling the {@link unlock} function or utilize the 122 | * {@link UniqueLock.try_lock_until} function instead to ensure the safety. 123 | * 124 | * @param at The maximum time point to wait. 125 | * @return Whether succeeded to monopoly the mutex or not. 126 | */ 127 | public async try_lock_until(at: Date): Promise { 128 | const ms: number = at.getTime() - Date.now(); 129 | return await this.try_lock_for(ms); 130 | } 131 | 132 | /** 133 | * Write unlocks the mutex. 134 | * 135 | * When you call this {@link unlock} method and there're someone who are currently blocked by 136 | * attempting to {@link lock write} or {@link lock_shared read} lock this mutex, one of them 137 | * (FIFO; first-in-first-out) would acquire the lock and continues its execution. 138 | * 139 | * Otherwise, there's not anyone who is acquiring the {@link lock write lock} of this mutex, 140 | * the {@link DomainError} exception would be thrown. 141 | * 142 | * > As you know, when you succeeded to acquire the `write lock`, you don't have to forget to 143 | * > calling this {@link unlock} method after your business. If you forget it, it would be a 144 | * > terrible situation for the others who're attempting to lock this mutex. 145 | * > 146 | * > However, if you utilize the {@link UniqueLock}, you don't need to consider about this 147 | * > {@link unlock} method. Just define your business into a callback function as a parameter 148 | * > of methods of the {@link UniqueLock}, then this {@link unlock} method would be 149 | * > automatically called by the {@link UniqueLock} after the business. 150 | * 151 | * @throw {@link DomainError} when no one is acquiring the {@link lock write lock}. 152 | */ 153 | public unlock(): Promise { 154 | return this.controller_.unlock(this.name_); 155 | } 156 | 157 | /* ----------------------------------------------------------- 158 | READ LOCK 159 | ----------------------------------------------------------- */ 160 | /** 161 | * Read locks the mutex. 162 | * 163 | * Shares a mutex until be {@link unlock_shared unlocked}. If there're someone who have 164 | * already {@link lock monopolied} the mutex, the function call would be blocked until all of 165 | * them to {@link unlock return} their acquisitions. 166 | * 167 | * In same reason, if you don't call the {@link unlock_shared} function after your business, 168 | * the others who want to {@link lock monopoly} the mutex would be fall into the forever 169 | * sleep. Therefore, never forget to calling the {@link unlock_shared} or utilize the 170 | * {@link SharedLock.lock} function instead to ensure the safety. 171 | */ 172 | public lock_shared(): Promise { 173 | return this.controller_.lock_shared(this.name_); 174 | } 175 | 176 | /** 177 | * Tries to read lock the mutex. 178 | * 179 | * Attemps to share a mutex without blocking. If succeeded to share the mutex immediately, it 180 | * returns `true` directly. Otherwise there's someone who has already {@link lock monopolied} 181 | * the mutex, the function gives up the trial immediately and returns `false` directly. 182 | * 183 | * Note that, if you succeeded to share the mutex (returns `true`) but do not call the 184 | * {@link unlock_shared} function after your buinsess, the others who want to 185 | * {@link lock monopoly} the mutex would be fall into the forever sleep. Therefore, never 186 | * forget to calling the {@link unlock_shared} function or utilize the 187 | * {@link SharedLock.try_lock} function instead to ensure the safety. 188 | * 189 | * @return Whether succeeded to share the mutex or not. 190 | */ 191 | public try_lock_shared(): Promise { 192 | return this.controller_.try_lock_shared(this.name_); 193 | } 194 | 195 | /** 196 | * Tries to read lock the mutex until timeout. 197 | * 198 | * Attemps to share a mutex until timeout. If succeeded to share the mutex until timeout, it 199 | * returns `true`. Otherwise failed to acquiring the shared lock in the given time, the 200 | * function gives up the trial and returns `false`. 201 | * 202 | * Failed to acquring the shared lock in the given time (returns `false`), it means that 203 | * there's someone who has already {@link lock monopolied} the mutex and does not return it 204 | * over the timeout. 205 | * 206 | * Note that, if you succeeded to share the mutex (returns `true`) but do not call the 207 | * {@link unlock_shared} function after your buinsess, the others who want to 208 | * {@link lock monopoly} the mutex would be fall into the forever sleep. Therefore, never 209 | * forget to calling the {@link unlock_shared} function or utilize the 210 | * {@link SharedLock.try_lock_for} function instead to ensure the safety. 211 | * 212 | * @param ms The maximum miliseconds for waiting. 213 | * @return Whether succeeded to share the mutex or not. 214 | */ 215 | public try_lock_shared_for(ms: number): Promise { 216 | return this.controller_.try_lock_shared_for(this.name_, ms); 217 | } 218 | 219 | /** 220 | * Tries to read lock the mutex until time expiration. 221 | * 222 | * Attemps to share a mutex until time expiration. If succeeded to share the mutex until time 223 | * expiration, it returns `true`. Otherwise failed to acquiring the shared lock in the given 224 | * time, the function gives up the trial and returns `false`. 225 | * 226 | * Failed to acquring the shared lock in the given time (returns `false`), it means that 227 | * there's someone who has already {@link lock monopolied} the mutex and does not return it 228 | * over the time expiration. 229 | * 230 | * Note that, if you succeeded to share the mutex (returns `true`) but do not call the 231 | * {@link unlock_shared} function after your buinsess, the others who want to 232 | * {@link lock monopoly} the mutex would be fall into the forever sleep. Therefore, never 233 | * forget to calling the {@link unlock_shared} function or utilize the 234 | * {@link SharedLock.try_lock_until} function instead to ensure the safety. 235 | * 236 | * @param at The maximum time point to wait. 237 | * @return Whether succeeded to share the mutex or not. 238 | */ 239 | public async try_lock_shared_until(at: Date): Promise { 240 | const ms: number = at.getTime() - Date.now(); 241 | return await this.try_lock_shared_for(ms); 242 | } 243 | 244 | /** 245 | * Read unlocks the mutex. 246 | * 247 | * When you call this {@link unlock_shared} method and there're someone who are currently 248 | * blocked by attempting to {@link lock monopoly} this mutex, one of them 249 | * (FIFO; first-in-first-out) would acquire the lock and continues its execution. 250 | * 251 | * Otherwise, there's not anyone who is acquiring the {@link lock_shared read lock} of this 252 | * mutex, the {@link DomainError} exception would be thrown. 253 | * 254 | * > As you know, when you succeeded to acquire the `read lock`, you don't have to forget to 255 | * > calling this {@link unlock_shared} method after your business. If you forget it, it would 256 | * > be a terrible situation for the others who're attempting to lock this mutex. 257 | * > 258 | * > However, if you utilize the {@link SharedLock}, you don't need to consider about this 259 | * > {@link unlock_shared} method. Just define your business into a callback function as a 260 | * > parameter of methods of the {@link SharedLock}, then this {@link unlock_shared} method 261 | * > would be automatically called by the {@link SharedLock} after the business. 262 | */ 263 | public unlock_shared(): Promise { 264 | return this.controller_.unlock_shared(this.name_); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/client/RemoteSemaphore.ts: -------------------------------------------------------------------------------- 1 | import { Promisive } from "tgrid"; 2 | import { Semaphore } from "tstl"; 3 | 4 | import { SemaphoresProvider } from "../server/providers/SemaphoresProvider"; 5 | 6 | /** 7 | * Remote Semaphore. 8 | * 9 | * @author Jeongho Nam - https://github.com/samchon 10 | */ 11 | export class RemoteSemaphore { 12 | /** 13 | * @hidden 14 | */ 15 | private controller_: Promisive; 16 | 17 | /** 18 | * @hidden 19 | */ 20 | private name_: string; 21 | 22 | /** 23 | * @hidden 24 | */ 25 | private max_: number; 26 | 27 | /* ----------------------------------------------------------- 28 | CONSTRUCTORS 29 | ----------------------------------------------------------- */ 30 | /** 31 | * @hidden 32 | */ 33 | private constructor( 34 | controller: Promisive, 35 | name: string, 36 | max: number, 37 | ) { 38 | this.controller_ = controller; 39 | this.name_ = name; 40 | this.max_ = max; 41 | } 42 | 43 | /** 44 | * @internal 45 | */ 46 | public static async create( 47 | controller: Promisive, 48 | name: string, 49 | count: number, 50 | ): Promise { 51 | const max: number = await controller.emplace(name, count); 52 | return new RemoteSemaphore(controller, name, max); 53 | } 54 | 55 | /** 56 | * Get number of maximum sections lockable. 57 | * 58 | * @return Number of maximum sections lockable. 59 | */ 60 | public max(): number { 61 | return this.max_; 62 | } 63 | 64 | /* ----------------------------------------------------------- 65 | LOCKERS 66 | ----------------------------------------------------------- */ 67 | /** 68 | * Acquires a section. 69 | * 70 | * Acquires a section until be {@link release released}. If all of the sections in the 71 | * semaphore already have been acquired by others, the function call would be blocked until 72 | * one of them returns its acquisition by calling the {@link release} method. 73 | * 74 | * In same reason, if you don't call the {@link release} function after you business, the 75 | * others who want to {@link acquire} a section from the semaphore would be fall into the 76 | * forever sleep. Therefore, never forget to calling the {@link release} function or utilize 77 | * the {@link UniqueLock.lock} function instead with {@link RemoteSemaphore.get_lockable} to 78 | * ensure the safety. 79 | */ 80 | public acquire(): Promise { 81 | return this.controller_.acquire(this.name_); 82 | } 83 | 84 | /** 85 | * Tries to acquire a section. 86 | * 87 | * Attempts to acquire a section without blocking. If succeeded to acquire a section from the 88 | * semaphore immediately, it returns `true` directly. Otherwise all of the sections in the 89 | * semaphore are full, the function gives up the trial immediately and returns `false` 90 | * directly. 91 | * 92 | * Note that, if you succeeded to acquire a section from the semaphore (returns `true) but do 93 | * not call the {@link release} function after your business, the others who want to 94 | * {@link acquire} a section from the semaphore would be fall into the forever sleep. 95 | * Therefore, never forget to calling the {@link release} function or utilize the 96 | * {@link UniqueLock.try_lock} function instead with {@link RemoteSemaphore.get_lockable} to 97 | * ensure the safety. 98 | * 99 | * @return Whether succeeded to acquire or not. 100 | */ 101 | public try_acquire(): Promise { 102 | return this.controller_.try_acquire(this.name_); 103 | } 104 | 105 | /** 106 | * Tries to acquire a section until timeout. 107 | * 108 | * Attempts to acquire a section from the semaphore until timeout. If succeeded to acquire a 109 | * section until the timeout, it returns `true`. Otherwise failed to acquiring a section in 110 | * given the time, the function gives up the trial and returns `false`. 111 | * 112 | * Failed to acquiring a section in the given time (returns `false`), it means that there're 113 | * someone who have already {@link acquire acquired} sections and do not return them over the 114 | * time expiration. 115 | * 116 | * Note that, if you succeeded to acquire a section from the semaphore (returns `true) but do 117 | * not call the {@link release} function after your business, the others who want to 118 | * {@link acquire} a section from the semaphore would be fall into the forever sleep. 119 | * Therefore, never forget to calling the {@link release} function or utilize the 120 | * {@link UniqueLock.try_acquire_for} function instead with 121 | * {@link RemoteSemaphore.get_lockable} to ensure the safety. 122 | * 123 | * @param ms The maximum miliseconds for waiting. 124 | * @return Whether succeded to acquire or not. 125 | */ 126 | public try_acquire_for(ms: number): Promise { 127 | return this.controller_.try_acquire_for(this.name_, ms); 128 | } 129 | 130 | /** 131 | * Tries to acquire a section until timeout. 132 | * 133 | * Attempts to acquire a section from the semaphore until time expiration. If succeeded to 134 | * acquire a section until the time expiration, it returns `true`. Otherwise failed to 135 | * acquiring a section in the given time, the function gives up the trial and returns `false`. 136 | * 137 | * Failed to acquiring a section in the given time (returns `false`), it means that there're 138 | * someone who have already {@link acquire acquired} sections and do not return them over the 139 | * time expiration. 140 | * 141 | * Note that, if you succeeded to acquire a section from the semaphore (returns `true) but do 142 | * not call the {@link release} function after your business, the others who want to 143 | * {@link acquire} a section from the semaphore would be fall into the forever sleep. 144 | * Therefore, never forget to calling the {@link release} function or utilize the 145 | * {@link UniqueLock.try_acquire_until} function instead with 146 | * {@link RemoteSemaphore.get_lockable} to ensure the safety. 147 | * 148 | * @param at The maximum time point to wait. 149 | * @return Whether succeded to acquire or not. 150 | */ 151 | public async try_acquire_until(at: Date): Promise { 152 | const ms: number = at.getTime() - Date.now(); 153 | return await this.try_acquire_for(ms); 154 | } 155 | 156 | /** 157 | * Release sections. 158 | * 159 | * When you call this {@link release} method and there're someone who are currently blocked 160 | * by attemping to {@link acquire} a section from this semaphore, *n* of them 161 | * (FIFO; first-in-first-out) would {@link acquire} those {@link release released} sections 162 | * and continue their executions. 163 | * 164 | * Otherwise, there's not anyone who is {@link acquire acquiring} the section or number of 165 | * the blocked are less than *n*, the {@link OutOfRange} error would be thrown. 166 | * 167 | * > As you know, when you succeeded to {@link acquire} a section, you don't have to forget 168 | * > to calling this {@link release} method after your business. If you forget it, it would 169 | * > be a terrible situation for the others who're attempting to {@link acquire} a section 170 | * > from this semaphore. 171 | * > 172 | * > However, if you utilize the {@link UniqueLock} with {@link RemoteSemaphore.get_lockable}, 173 | * > you don't need to consider about this {@link release} method. Just define your business 174 | * > into a callback function as a parameter of methods of the {@link UniqueLock}, then this 175 | * > {@link release} method would be automatically called by the {@link UniqueLock} after the 176 | * > business. 177 | * 178 | * @param n Number of sections to be released. Default is 1. 179 | * @throw {@link OutOfRange} when *n* is greater than currently {@link acquire acquired} sections. 180 | */ 181 | public release(count: number = 1): Promise { 182 | return this.controller_.release(this.name_, count); 183 | } 184 | } 185 | 186 | /** 187 | * 188 | */ 189 | export namespace RemoteSemaphore { 190 | /** 191 | * Capsules a {@link RemoteSemaphore} to be suitable for the {@link UniqueLock}. 192 | * 193 | * @param semaphore Target semaphore to capsule. 194 | * @return Lockable instance suitable for the {@link UniqueLock} 195 | */ 196 | export import get_lockable = Semaphore.get_lockable; 197 | } 198 | -------------------------------------------------------------------------------- /src/client/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./RemoteBarrier"; 2 | export * from "./RemoteConditionVariable"; 3 | export * from "./RemoteLatch"; 4 | export * from "./RemoteMutex"; 5 | export * from "./RemoteSemaphore"; 6 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @packageDocumentation 3 | * @module msv 4 | */ 5 | //----------------------------------------------------------- 6 | import * as msv from "./module"; 7 | 8 | export default msv; 9 | export * from "./module"; 10 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @packageDocumentation 3 | * @module msv 4 | */ 5 | //----------------------------------------------------------- 6 | export * from "./MutexServer"; 7 | export * from "./MutexAcceptor"; 8 | export * from "./MutexConnector"; 9 | 10 | export * from "./client/index"; 11 | -------------------------------------------------------------------------------- /src/server/GlobalGroup.ts: -------------------------------------------------------------------------------- 1 | import { GlobalBarriers } from "./global/GlobalBarriers"; 2 | import { GlobalConditionVariablaes } from "./global/GlobalConditionVariables"; 3 | import { GlobalLatches } from "./global/GlobalLatches"; 4 | import { GlobalMutexes } from "./global/GlobalMutexes"; 5 | import { GlobalSemaphores } from "./global/GlobalSemaphores"; 6 | 7 | /** 8 | * @internal 9 | */ 10 | export class GlobalGroup { 11 | // MAIN COMPONENTS 12 | public condition_variables: GlobalConditionVariablaes = 13 | new GlobalConditionVariablaes(); 14 | public mutexes: GlobalMutexes = new GlobalMutexes(); 15 | public semaphores: GlobalSemaphores = new GlobalSemaphores(); 16 | 17 | // ADAPTOR COMPONENTS 18 | public barriers: GlobalBarriers = new GlobalBarriers(); 19 | public latches: GlobalLatches = new GlobalLatches(); 20 | } 21 | -------------------------------------------------------------------------------- /src/server/ProviderGroup.ts: -------------------------------------------------------------------------------- 1 | import { WebSocketAcceptor } from "tgrid"; 2 | import { Joiner } from "./components/internal/Joiner"; 3 | 4 | import { GlobalGroup } from "./GlobalGroup"; 5 | import { ConditionVariablesProvider } from "./providers/ConditionVariablesProvider"; 6 | import { SemaphoresProvider } from "./providers/SemaphoresProvider"; 7 | import { MutexesProvider } from "./providers/MutexesProvider"; 8 | import { BarriersProvider } from "./providers/BarriersProvider"; 9 | import { LatchesProvider } from "./providers/LatchesProvider"; 10 | import { List } from "tstl"; 11 | 12 | /** 13 | * @internal 14 | */ 15 | export class ProviderGroup { 16 | private readonly disolvers_: List; 17 | 18 | // MAIN COMPONENTS 19 | public readonly condition_variables: ConditionVariablesProvider; 20 | public readonly mutexes: MutexesProvider; 21 | public readonly semaphores: SemaphoresProvider; 22 | 23 | // ADAPTOR COMPONENTS 24 | public readonly barriers: BarriersProvider; 25 | public readonly latches: LatchesProvider; 26 | 27 | public constructor( 28 | group: GlobalGroup, 29 | acceptor: WebSocketAcceptor, 30 | ) { 31 | this.disolvers_ = new List(); 32 | 33 | this.condition_variables = new ConditionVariablesProvider( 34 | group.condition_variables, 35 | acceptor, 36 | this.disolvers_, 37 | "RemoteConditionVariable", 38 | ); 39 | this.mutexes = new MutexesProvider( 40 | group.mutexes, 41 | acceptor, 42 | this.disolvers_, 43 | "RemoteMutex", 44 | ); 45 | this.semaphores = new SemaphoresProvider( 46 | group.semaphores, 47 | acceptor, 48 | this.disolvers_, 49 | "RemoteSemaphore", 50 | ); 51 | 52 | this.barriers = new BarriersProvider( 53 | group.barriers, 54 | acceptor, 55 | this.disolvers_, 56 | "RemoteBarrier", 57 | ); 58 | this.latches = new LatchesProvider( 59 | group.latches, 60 | acceptor, 61 | this.disolvers_, 62 | "RemoteLatch", 63 | ); 64 | } 65 | 66 | public async destructor(): Promise { 67 | for (const joiner of this.disolvers_) 68 | if (joiner !== undefined) await joiner(); 69 | 70 | await this.condition_variables.destructor(); 71 | await this.mutexes.destructor(); 72 | await this.semaphores.destructor(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/server/components/IComponent.ts: -------------------------------------------------------------------------------- 1 | import { WebSocketAcceptor } from "tgrid"; 2 | import { ProviderGroup } from "../ProviderGroup"; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export interface IComponent { 8 | _Insert_acceptor(acceptor: WebSocketAcceptor): void; 9 | _Erase_acceptor( 10 | acceptor: WebSocketAcceptor, 11 | ): boolean; 12 | } 13 | -------------------------------------------------------------------------------- /src/server/components/ServerBarrier.ts: -------------------------------------------------------------------------------- 1 | import { WebSocketAcceptor } from "tgrid"; 2 | import { List } from "tstl"; 3 | 4 | import { IComponent } from "./IComponent"; 5 | import { ServerConditionVariable } from "./ServerConditionVariable"; 6 | import { Joiner } from "./internal/Joiner"; 7 | import { ProviderGroup } from "../ProviderGroup"; 8 | 9 | /** 10 | * @internal 11 | */ 12 | export class ServerBarrier implements IComponent { 13 | private cv_: ServerConditionVariable; 14 | 15 | private size_: number; 16 | private count_: number; 17 | 18 | /* --------------------------------------------------------- 19 | CONSTRUCTORS 20 | --------------------------------------------------------- */ 21 | public constructor(size: number) { 22 | this.cv_ = new ServerConditionVariable(); 23 | 24 | this.size_ = size; 25 | this.count_ = size; 26 | } 27 | 28 | public _Insert_acceptor( 29 | acceptor: WebSocketAcceptor, 30 | ): void { 31 | this.cv_._Insert_acceptor(acceptor); 32 | } 33 | 34 | public _Erase_acceptor( 35 | acceptor: WebSocketAcceptor, 36 | ): boolean { 37 | return this.cv_._Erase_acceptor(acceptor); 38 | } 39 | 40 | /* --------------------------------------------------------- 41 | WAITORS 42 | --------------------------------------------------------- */ 43 | public wait( 44 | acceptor: WebSocketAcceptor, 45 | disolver: List.Iterator, 46 | ): Promise { 47 | return this.cv_.wait(acceptor, disolver); 48 | } 49 | 50 | public wait_for( 51 | ms: number, 52 | acceptor: WebSocketAcceptor, 53 | disolver: List.Iterator, 54 | ): Promise { 55 | return this.cv_.wait_for(ms, acceptor, disolver); 56 | } 57 | 58 | /* --------------------------------------------------------- 59 | ARRIVERS 60 | --------------------------------------------------------- */ 61 | public async arrive(n: number): Promise { 62 | const completed: boolean = (this.count_ += n) <= this.size_; 63 | if (completed === false) return; 64 | 65 | this.count_ %= this.size_; 66 | await this.cv_.notify_all(); 67 | } 68 | 69 | public async arrive_and_wait( 70 | acceptor: WebSocketAcceptor, 71 | disolver: List.Iterator, 72 | ): Promise { 73 | await this.arrive(1); 74 | await this.wait(acceptor, disolver); 75 | } 76 | 77 | public async arrive_and_drop(): Promise { 78 | --this.size_; 79 | await this.arrive(0); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/server/components/ServerConditionVariable.ts: -------------------------------------------------------------------------------- 1 | import { WebSocketAcceptor } from "tgrid"; 2 | import { List, sleep_for } from "tstl"; 3 | import { LockType } from "tstl/lib/internal/thread/LockType"; 4 | 5 | import { SolidComponent } from "./SolidComponent"; 6 | import { Joiner } from "./internal/Joiner"; 7 | import { ProviderGroup } from "../ProviderGroup"; 8 | 9 | /** 10 | * @internal 11 | */ 12 | export class ServerConditionVariable extends SolidComponent { 13 | /* --------------------------------------------------------- 14 | WAITORS 15 | --------------------------------------------------------- */ 16 | public wait( 17 | acceptor: WebSocketAcceptor, 18 | disolver: List.Iterator, 19 | ): Promise { 20 | return new Promise((resolve) => { 21 | // ENROLL TO THE RESOLVERS 22 | const it: List.Iterator = this._Insert_resolver({ 23 | handler: resolve, 24 | lockType: LockType.HOLD, 25 | 26 | acceptor: acceptor, 27 | disolver: disolver, 28 | aggregate: {}, 29 | }); 30 | 31 | // DISCONNECTION HANDLER 32 | disolver.value = () => this._Cancel_wait(it); 33 | }); 34 | } 35 | 36 | public wait_for( 37 | ms: number, 38 | acceptor: WebSocketAcceptor, 39 | disolver: List.Iterator, 40 | ): Promise { 41 | return new Promise((resolve) => { 42 | // ENROLL TO THE RESOLVERS 43 | const it: List.Iterator = this._Insert_resolver({ 44 | handler: resolve, 45 | lockType: LockType.KNOCK, 46 | 47 | acceptor: acceptor, 48 | disolver: disolver, 49 | aggregate: {}, 50 | }); 51 | 52 | // DISCONNECTION HANDLER 53 | disolver.value = () => this._Cancel_wait(it); 54 | 55 | // TIME EXPIRATION HANDLER 56 | sleep_for(ms).then(() => { 57 | resolve(this._Cancel_wait(it) === false); 58 | }); 59 | }); 60 | } 61 | 62 | private _Cancel_wait(it: List.Iterator): boolean { 63 | if (it.value.handler === null) return false; 64 | 65 | it.value.destructor!(); 66 | this.queue_.erase(it); 67 | 68 | return true; 69 | } 70 | 71 | /* --------------------------------------------------------- 72 | NOTIFIERS 73 | --------------------------------------------------------- */ 74 | public async notify_one(): Promise { 75 | if (this.queue_.empty()) return; 76 | 77 | // POP THE FIRST ITEM 78 | const it: List.Iterator = this.queue_.begin(); 79 | this.queue_.erase(it); 80 | 81 | // DO RESOLVE 82 | this._Notify(it.value, true); 83 | } 84 | 85 | public async notify_all(): Promise { 86 | if (this.queue_.empty()) return; 87 | 88 | // COPY RESOLVERS 89 | const ordinaryResolvers: Resolver[] = [...this.queue_]; 90 | const copiedResolvers: Resolver[] = ordinaryResolvers.map((resolver) => ({ 91 | ...resolver, 92 | })); 93 | 94 | // CLEAR OLD ITEMS 95 | this.queue_.clear(); 96 | for (const resolver of ordinaryResolvers) resolver.destructor!(); 97 | 98 | // DO NOTIFY 99 | for (const resolver of copiedResolvers) this._Notify(resolver, false); 100 | } 101 | 102 | private _Notify(resolver: Resolver, discard: boolean): void { 103 | // RESERVE HANDLER 104 | const handler = resolver.handler!; 105 | 106 | // DISCARD FOR SEQUENCE 107 | if (discard === true) resolver.destructor!(); 108 | 109 | // CALL HANDLER 110 | if (resolver.lockType === LockType.HOLD) handler(); 111 | else handler(true); 112 | } 113 | } 114 | 115 | /** 116 | * @internal 117 | */ 118 | type Resolver = SolidComponent.Resolver; 119 | -------------------------------------------------------------------------------- /src/server/components/ServerLatch.ts: -------------------------------------------------------------------------------- 1 | import { WebSocketAcceptor } from "tgrid"; 2 | import { List } from "tstl"; 3 | 4 | import { IComponent } from "./IComponent"; 5 | import { ServerConditionVariable } from "./ServerConditionVariable"; 6 | import { Joiner } from "./internal/Joiner"; 7 | import { ProviderGroup } from "../ProviderGroup"; 8 | 9 | /** 10 | * @internal 11 | */ 12 | export class ServerLatch implements IComponent { 13 | private cv_: ServerConditionVariable; 14 | private count_: number; 15 | 16 | /* --------------------------------------------------------- 17 | CONSTRUCTORS 18 | --------------------------------------------------------- */ 19 | public constructor(size: number) { 20 | this.cv_ = new ServerConditionVariable(); 21 | this.count_ = size; 22 | } 23 | 24 | public _Insert_acceptor( 25 | acceptor: WebSocketAcceptor, 26 | ): void { 27 | this.cv_._Insert_acceptor(acceptor); 28 | } 29 | 30 | public _Erase_acceptor( 31 | acceptor: WebSocketAcceptor, 32 | ): boolean { 33 | return this.cv_._Erase_acceptor(acceptor); 34 | } 35 | 36 | /* --------------------------------------------------------- 37 | WAITORS 38 | --------------------------------------------------------- */ 39 | public async wait( 40 | acceptor: WebSocketAcceptor, 41 | disolver: List.Iterator, 42 | ): Promise { 43 | if (this._Try_wait() === false) return this.cv_.wait(acceptor, disolver); 44 | } 45 | 46 | public async try_wait(): Promise { 47 | return this._Try_wait(); 48 | } 49 | 50 | public async wait_for( 51 | ms: number, 52 | acceptor: WebSocketAcceptor, 53 | disolver: List.Iterator, 54 | ): Promise { 55 | if (this._Try_wait() === true) return true; 56 | return await this.cv_.wait_for(ms, acceptor, disolver); 57 | } 58 | 59 | private _Try_wait(): boolean { 60 | return this.count_ <= 0; 61 | } 62 | 63 | /* --------------------------------------------------------- 64 | DECOUNTERS 65 | --------------------------------------------------------- */ 66 | public async count_down(n: number): Promise { 67 | this.count_ -= n; 68 | if (this._Try_wait() === true) await this.cv_.notify_all(); 69 | } 70 | 71 | public async arrive_and_wait( 72 | acceptor: WebSocketAcceptor, 73 | disolver: List.Iterator, 74 | ): Promise { 75 | await this.count_down(1); 76 | await this.wait(acceptor, disolver); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/server/components/ServerMutex.ts: -------------------------------------------------------------------------------- 1 | import { WebSocketAcceptor } from "tgrid"; 2 | import { List, sleep_for, OutOfRange, Pair } from "tstl"; 3 | import { AccessType } from "tstl/lib/internal/thread/AccessType"; 4 | import { LockType } from "tstl/lib/internal/thread/LockType"; 5 | 6 | import { SolidComponent } from "./SolidComponent"; 7 | import { Disolver } from "./internal/Disolver"; 8 | import { Joiner } from "./internal/Joiner"; 9 | import { ProviderGroup } from "../ProviderGroup"; 10 | 11 | /** 12 | * @internal 13 | */ 14 | export class ServerMutex extends SolidComponent { 15 | private writing_: number; 16 | private reading_: number; 17 | 18 | /* --------------------------------------------------------- 19 | CONSTRUCTORS 20 | --------------------------------------------------------- */ 21 | public constructor() { 22 | super(); 23 | 24 | this.writing_ = 0; 25 | this.reading_ = 0; 26 | } 27 | 28 | protected _Insert_resolver(resolver: Resolver): List.Iterator { 29 | const it: List.Iterator = super._Insert_resolver(resolver); 30 | resolver.iterator = it; 31 | 32 | return it; 33 | } 34 | 35 | /* --------------------------------------------------------- 36 | WRITE LOCK 37 | --------------------------------------------------------- */ 38 | public lock( 39 | acceptor: WebSocketAcceptor, 40 | disolver: List.Iterator, 41 | ): Promise { 42 | return new Promise((resolve) => { 43 | // CONSTRUCT RESOLVER 44 | const it: List.Iterator = this._Insert_resolver({ 45 | handler: this.writing_++ === 0 && this.reading_ === 0 ? null : resolve, 46 | accessType: AccessType.WRITE, 47 | lockType: LockType.HOLD, 48 | 49 | acceptor: acceptor, 50 | disolver: disolver, 51 | aggregate: {}, 52 | }); 53 | 54 | // DISCONNECTION HANDLER 55 | disolver.value = () => this._Handle_disconnection(it); 56 | 57 | // RETURNS OR WAIT 58 | if (it.value.handler === null) resolve(); 59 | }); 60 | } 61 | 62 | public async try_lock( 63 | acceptor: WebSocketAcceptor, 64 | disolver: List.Iterator, 65 | ): Promise { 66 | // LOCKABLE ? 67 | if (this.writing_ !== 0 || this.reading_ !== 0) return false; 68 | 69 | // CONSTRUCT RESOLVER 70 | const it: List.Iterator = this._Insert_resolver({ 71 | handler: null, 72 | accessType: AccessType.WRITE, 73 | lockType: LockType.KNOCK, 74 | 75 | acceptor: acceptor, 76 | disolver: disolver, 77 | aggregate: {}, 78 | }); 79 | 80 | // DISCONNECTION HANDLER 81 | disolver.value = () => this._Handle_disconnection(it); 82 | 83 | // RETURNS 84 | ++this.writing_; 85 | return true; 86 | } 87 | 88 | public try_lock_for( 89 | ms: number, 90 | acceptor: WebSocketAcceptor, 91 | disolver: List.Iterator, 92 | ): Promise { 93 | return new Promise((resolve) => { 94 | // CONSTRUCT RESOLVER 95 | const it: List.Iterator = this._Insert_resolver({ 96 | handler: this.writing_++ === 0 && this.reading_ === 0 ? null : resolve, 97 | accessType: AccessType.WRITE, 98 | lockType: LockType.KNOCK, 99 | 100 | acceptor: acceptor, 101 | disolver: disolver, 102 | aggregate: {}, 103 | }); 104 | 105 | // DISCONNECTION HANDLER 106 | disolver.value = () => this._Handle_disconnection(it); 107 | 108 | // RETURNS OR WAIT UNTIL TIMEOUT 109 | if (it.value.handler === null) 110 | resolve(true); // SUCCESS 111 | else 112 | sleep_for(ms).then(() => { 113 | // NOT YET, THEN DO UNLOCK 114 | if (it.value.handler !== null) { 115 | --this.writing_; 116 | this._Cancel(it); 117 | } 118 | }); 119 | }); 120 | } 121 | 122 | public async unlock( 123 | acceptor: WebSocketAcceptor, 124 | ): Promise { 125 | //---- 126 | // VALIDATION 127 | //---- 128 | // IN GLOBAL AREA 129 | if ( 130 | this.queue_.empty() === true || 131 | this.queue_.front().accessType !== AccessType.WRITE 132 | ) 133 | throw new OutOfRange( 134 | `Error on RemoteMutex.unlock(): this mutex is free on the unique lock.`, 135 | ); 136 | 137 | // IN LOCAL AREA 138 | const local: SolidComponent.LocalArea | null = 139 | this._Get_local_area(acceptor); 140 | if ( 141 | local === null || 142 | local.queue.empty() === true || 143 | this.queue_.front() !== local.queue.front() 144 | ) 145 | throw new OutOfRange( 146 | "Error on RemoteMutex.unlock(): you're free on the unique lock.", 147 | ); 148 | 149 | //---- 150 | // RELEASE 151 | //---- 152 | // DESTRUCT TOP RESOLVER 153 | const top: Resolver = local.queue.front(); 154 | 155 | this.queue_.erase(top.iterator!); 156 | top.destructor!(); 157 | 158 | // DO RELEASE 159 | --this.writing_; 160 | this._Release(); 161 | } 162 | 163 | /* --------------------------------------------------------- 164 | READ LOCK 165 | --------------------------------------------------------- */ 166 | public lock_shared( 167 | acceptor: WebSocketAcceptor, 168 | disolver: List.Iterator, 169 | ): Promise { 170 | return new Promise((resolve) => { 171 | // CONSTRUCT RESOLVER 172 | const it: List.Iterator = this._Insert_resolver({ 173 | handler: this.writing_ === 0 ? null : resolve, 174 | accessType: AccessType.READ, 175 | lockType: LockType.HOLD, 176 | 177 | acceptor: acceptor, 178 | disolver: disolver, 179 | aggregate: {}, 180 | }); 181 | 182 | // DISCONNECTION HANDLER 183 | disolver.value = () => this._Handle_disconnection(it); 184 | 185 | // RETURNS OR WAIT 186 | ++this.reading_; 187 | if (it.value.handler === null) resolve(); 188 | }); 189 | } 190 | 191 | public async try_lock_shared( 192 | acceptor: WebSocketAcceptor, 193 | disolver: List.Iterator, 194 | ): Promise { 195 | if (this.writing_ !== 0) return false; 196 | 197 | // CONSTRUCT RESOLVER 198 | const it = this._Insert_resolver({ 199 | handler: null, 200 | accessType: AccessType.READ, 201 | lockType: LockType.KNOCK, 202 | 203 | acceptor: acceptor, 204 | disolver: disolver, 205 | aggregate: {}, 206 | }); 207 | 208 | // DISCONNECTION HANDLER 209 | disolver.value = () => this._Handle_disconnection(it); 210 | 211 | // RETURNS 212 | ++this.reading_; 213 | return true; 214 | } 215 | 216 | public try_lock_shared_for( 217 | ms: number, 218 | acceptor: WebSocketAcceptor, 219 | disolver: List.Iterator, 220 | ): Promise { 221 | return new Promise((resolve) => { 222 | // CONSTRUCT RESOLVER 223 | const it: List.Iterator = this._Insert_resolver({ 224 | handler: this.writing_ === 0 ? null : resolve, 225 | accessType: AccessType.READ, 226 | lockType: LockType.KNOCK, 227 | 228 | acceptor: acceptor, 229 | disolver: disolver, 230 | aggregate: {}, 231 | }); 232 | 233 | // DISCONNECTION HANDLER 234 | disolver.value = () => this._Handle_disconnection(it); 235 | 236 | // RETURNS OR WAIT UNTIL TIMEOUT 237 | ++this.reading_; 238 | if (it.value.handler === null) resolve(true); 239 | else 240 | sleep_for(ms).then(() => { 241 | if (it.value.handler !== null) { 242 | --this.reading_; 243 | this._Cancel(it); 244 | } 245 | }); 246 | }); 247 | } 248 | 249 | public async unlock_shared( 250 | acceptor: WebSocketAcceptor, 251 | ): Promise { 252 | //---- 253 | // VALIDATION 254 | //---- 255 | // IN GLOBAL AREA 256 | if ( 257 | this.queue_.empty() === true || 258 | this.queue_.front().accessType !== AccessType.READ 259 | ) 260 | throw new OutOfRange( 261 | `Error on RemoteMutex.unlock_shared(): this mutex is free on the shared lock.`, 262 | ); 263 | 264 | // IN LOCAL AREA 265 | const local: SolidComponent.LocalArea | null = 266 | this._Get_local_area(acceptor); 267 | if ( 268 | local === null || 269 | local.queue.empty() === true || 270 | local.queue.front().accessType !== AccessType.READ 271 | ) 272 | throw new OutOfRange( 273 | "Error on RemoteMutex.unlock_shared(): you're free on the shared lock.", 274 | ); 275 | 276 | //---- 277 | // RELEASE 278 | //---- 279 | // DESTRUCT THE RESOLVER 280 | const top: Resolver = local.queue.front(); 281 | 282 | this.queue_.erase(top.iterator!); 283 | top.destructor!(); 284 | 285 | // DO RELEASE 286 | --this.reading_; 287 | this._Release(); 288 | } 289 | 290 | /* --------------------------------------------------------- 291 | RELEASE 292 | --------------------------------------------------------- */ 293 | private _Release(): void { 294 | if (this.queue_.empty() === true) return; 295 | 296 | // GATHER THE NEXT STEPS 297 | const currentType: AccessType = this.queue_.front().accessType; 298 | const pairList: Pair[] = []; 299 | 300 | for (const resolver of this.queue_) { 301 | // STOP WHEN DIFFERENT ACCESS TYPE COMES 302 | if (resolver.accessType !== currentType) break; 303 | // RESERVE HANDLER 304 | else if (resolver.handler !== null) { 305 | pairList.push(new Pair(resolver.handler, resolver.lockType)); 306 | resolver.handler = null; 307 | } 308 | 309 | // STOP AFTER WRITE LOCK 310 | if (resolver.accessType === AccessType.WRITE) break; 311 | } 312 | 313 | // CALL THE HANDLERS 314 | for (const pair of pairList) 315 | if (pair.second === LockType.HOLD) pair.first(); 316 | else pair.first(true); 317 | } 318 | 319 | private _Cancel(it: List.Iterator): void { 320 | // POP HANDLER & DESTRUCT 321 | const handler: Function = it.value.handler!; 322 | 323 | this.queue_.erase(it); 324 | it.value.destructor!(); 325 | 326 | // CHECK THE PREVIOUS HANDLER 327 | const prev: List.Iterator = it.prev(); 328 | if (prev.equals(this.queue_.end()) === false && prev.value.handler === null) 329 | this._Release(); 330 | 331 | // (LAZY) RETURNS FAILURE 332 | handler(false); 333 | } 334 | 335 | private async _Handle_disconnection( 336 | it: List.Iterator, 337 | ): Promise { 338 | // CHECK ALIVE 339 | if (((it) as Disolver).erased_ === true) return; 340 | //---- 341 | // ROLLBACK ACTION 342 | //---- 343 | else if (it.value.handler === null) { 344 | // SUCCEEDED TO GET LOCK 345 | if (it.value.accessType === AccessType.WRITE) 346 | await this.unlock(it.value.acceptor); 347 | else await this.unlock_shared(it.value.acceptor); 348 | } else { 349 | // HAD BEEN WAITING 350 | if (it.value.accessType === AccessType.WRITE) --this.writing_; 351 | else --this.reading_; 352 | 353 | // CANCEL THE LOCK 354 | this._Cancel(it); 355 | } 356 | } 357 | } 358 | 359 | /** 360 | * @internal 361 | */ 362 | interface Resolver extends SolidComponent.Resolver { 363 | accessType: AccessType; 364 | } 365 | -------------------------------------------------------------------------------- /src/server/components/ServerSemaphore.ts: -------------------------------------------------------------------------------- 1 | import { WebSocketAcceptor } from "tgrid"; 2 | import { List, sleep_for, OutOfRange, Pair } from "tstl"; 3 | import { LockType } from "tstl/lib/internal/thread/LockType"; 4 | 5 | import { SolidComponent } from "./SolidComponent"; 6 | import { Disolver } from "./internal/Disolver"; 7 | import { ProviderGroup } from "../ProviderGroup"; 8 | 9 | /** 10 | * @internal 11 | */ 12 | export class ServerSemaphore extends SolidComponent { 13 | private max_: number; 14 | private acquiring_: number; 15 | 16 | /* --------------------------------------------------------- 17 | CONSTRUCTORS 18 | --------------------------------------------------------- */ 19 | public constructor(max: number) { 20 | super(); 21 | 22 | this.max_ = max; 23 | this.acquiring_ = 0; 24 | } 25 | 26 | public max(): number { 27 | return this.max_; 28 | } 29 | 30 | /* --------------------------------------------------------- 31 | ACQUIRANCES 32 | --------------------------------------------------------- */ 33 | public acquire( 34 | acceptor: WebSocketAcceptor, 35 | disolver: Disolver, 36 | ): Promise { 37 | return new Promise((resolve) => { 38 | const success: boolean = this.acquiring_ < this.max_; 39 | 40 | // CONSTRUCT RESOLVER 41 | const it: List.Iterator = this._Insert_resolver({ 42 | handler: success ? null : resolve, 43 | lockType: LockType.HOLD, 44 | acceptor: acceptor, 45 | disolver: disolver, 46 | aggregate: { 47 | acquring: success ? 1 : 0, 48 | }, 49 | }); 50 | 51 | // DISCONNECTION HANDLER 52 | disolver.value = () => this._Handle_disconnection(it); 53 | 54 | // RETURNS OR WAIT 55 | if (it.value.handler === null) { 56 | ++this.acquiring_; 57 | resolve(); 58 | } 59 | }); 60 | } 61 | 62 | public async try_acquire( 63 | acceptor: WebSocketAcceptor, 64 | disolver: Disolver, 65 | ): Promise { 66 | // ACQUIRABLE ? 67 | if (this.acquiring_ >= this.max_) return false; 68 | 69 | // CONSTRUCT RESOLVER 70 | const it: List.Iterator = this._Insert_resolver({ 71 | handler: null, 72 | lockType: LockType.HOLD, 73 | acceptor: acceptor, 74 | disolver: disolver, 75 | aggregate: { 76 | acquring: 1, 77 | }, 78 | }); 79 | ++this.acquiring_; 80 | 81 | // RETURNS WITH DISCONNECTION HANDLER 82 | disolver.value = () => this._Handle_disconnection(it); 83 | return true; 84 | } 85 | 86 | public async try_acquire_for( 87 | ms: number, 88 | acceptor: WebSocketAcceptor, 89 | disolver: Disolver, 90 | ): Promise { 91 | return new Promise((resolve) => { 92 | const success: boolean = this.acquiring_ < this.max_; 93 | 94 | // CONSTRUCT RESOLVER 95 | const it: List.Iterator = this._Insert_resolver({ 96 | handler: success ? null : resolve, 97 | lockType: LockType.KNOCK, 98 | acceptor: acceptor, 99 | disolver: disolver, 100 | aggregate: { 101 | acquring: success ? 1 : 0, 102 | }, 103 | }); 104 | 105 | // DISCONNECTION HANDLER 106 | disolver.value = () => this._Handle_disconnection(it); 107 | 108 | // RETURNS OR WAIT UNTIL TIMEOUT 109 | if (it.value.handler === null) { 110 | ++this.acquiring_; 111 | resolve(true); 112 | } else 113 | sleep_for(ms).then(() => { 114 | const success: boolean = it.value.handler === null; 115 | if (success === false) this._Cancel(it); 116 | 117 | resolve(success); 118 | }); 119 | }); 120 | } 121 | 122 | private _Cancel(it: List.Iterator): void { 123 | // POP THE LISTENER 124 | const handler: Function = it.value.handler!; 125 | 126 | this.queue_.erase(it); 127 | it.value.destructor!(); 128 | 129 | // RETURNS FAILURE 130 | handler(false); 131 | } 132 | 133 | /* --------------------------------------------------------- 134 | RELEASE 135 | --------------------------------------------------------- */ 136 | public async release( 137 | n: number, 138 | acceptor: WebSocketAcceptor, 139 | ): Promise { 140 | //---- 141 | // VALIDATION 142 | //---- 143 | // IN GLOBAL AREA 144 | if (n < 1) 145 | throw new OutOfRange( 146 | `Error on RemoteSemaphore.release(): parametric n is less than 1 -> (n = ${n}).`, 147 | ); 148 | else if (n > this.max_) 149 | throw new OutOfRange( 150 | `Error on RemoteSemaphore.release(): parametric n is greater than max -> (n = ${n}, max = ${this.max_}).`, 151 | ); 152 | else if (n > this.acquiring_) 153 | throw new OutOfRange( 154 | `Error on RemoteSemaphore.release(): parametric n is greater than acquiring -> (n = ${n}, acquiring = ${this.acquiring_}).`, 155 | ); 156 | 157 | // IN LOCAL AREA 158 | const local: SolidComponent.LocalArea | null = 159 | this._Get_local_area(acceptor); 160 | if (local === null || local.queue.empty() === true) 161 | throw new OutOfRange( 162 | "Error on RemoteSemaphore.release(): you're free on the acquirance.", 163 | ); 164 | else if (local.aggregate.acquring < n) 165 | throw new OutOfRange( 166 | `Error onRemoteSemaphore.release(): parametric n is greater than what you've been acquiring -> (n = ${n}, acquiring = ${local.aggregate.acquring}).`, 167 | ); 168 | 169 | //---- 170 | // RELEASE 171 | //---- 172 | // ERASE FROM QUEUES 173 | this.acquiring_ -= n; 174 | local.aggregate.acquring -= n; 175 | 176 | // eslint-disable-next-line 177 | let count: number = 0; 178 | for ( 179 | // eslint-disable-next-line 180 | let it = local.queue.begin(); 181 | it.equals(local.queue.end()) === false; 182 | 183 | ) { 184 | this.queue_.erase(it.value.iterator!); 185 | it = it.value.destructor!(); 186 | 187 | if (++count === n) break; 188 | } 189 | 190 | // RESERVE HANDLERS 191 | const pairList: Pair[] = []; 192 | 193 | for (const resolver of this.queue_) 194 | if (resolver.handler !== null) { 195 | pairList.push(new Pair(resolver.handler!, resolver.lockType)); 196 | resolver.handler = null; 197 | 198 | ++resolver.aggregate.acquring; 199 | if (++this.acquiring_ === this.max_) break; 200 | } 201 | 202 | // CALL HANDLERS 203 | for (const pair of pairList) 204 | if (pair.second === LockType.HOLD) pair.first(); 205 | else pair.first(true); 206 | } 207 | 208 | private async _Handle_disconnection( 209 | it: List.Iterator, 210 | ): Promise { 211 | // CHECK ALIVE 212 | if (((it) as Disolver).erased_ === true) return; 213 | // ROLLBACK ACTION 214 | else if (it.value.handler === null) 215 | await this.release(1, it.value.acceptor); 216 | else this._Cancel(it); 217 | } 218 | } 219 | 220 | /** 221 | * @internal 222 | */ 223 | type Resolver = SolidComponent.Resolver; 224 | 225 | /** 226 | * @internal 227 | */ 228 | type Aggregate = Record<"acquring", number>; 229 | -------------------------------------------------------------------------------- /src/server/components/SolidComponent.ts: -------------------------------------------------------------------------------- 1 | import { WebSocketAcceptor } from "tgrid"; 2 | import { HashMap, HashSet, List } from "tstl"; 3 | import { LockType } from "tstl/lib/internal/thread/LockType"; 4 | 5 | import { IComponent } from "./IComponent"; 6 | import { Disolver } from "./internal/Disolver"; 7 | import { ProviderGroup } from "../ProviderGroup"; 8 | 9 | /** 10 | * @internal 11 | */ 12 | export abstract class SolidComponent< 13 | Resolver extends SolidComponent.Resolver, 14 | Aggregate extends Record, 15 | > implements IComponent 16 | { 17 | protected queue_: List; 18 | protected local_areas_: HashMap< 19 | WebSocketAcceptor, 20 | SolidComponent.LocalArea 21 | >; 22 | 23 | private acceptors_: HashSet> = 24 | new HashSet(); 25 | 26 | /* --------------------------------------------------------- 27 | CONSTRUCTORS 28 | --------------------------------------------------------- */ 29 | public constructor() { 30 | this.queue_ = new List(); 31 | this.local_areas_ = new HashMap(); 32 | } 33 | 34 | public _Insert_acceptor( 35 | acceptor: WebSocketAcceptor, 36 | ): void { 37 | this.acceptors_.insert(acceptor); 38 | } 39 | 40 | public _Erase_acceptor( 41 | acceptor: WebSocketAcceptor, 42 | ): boolean { 43 | this.acceptors_.erase(acceptor); 44 | return this.acceptors_.empty(); 45 | } 46 | 47 | /* --------------------------------------------------------- 48 | ACCESSORS 49 | --------------------------------------------------------- */ 50 | protected _Insert_resolver(resolver: Resolver): List.Iterator { 51 | //---- 52 | // LOCAL QUEUE 53 | //---- 54 | // FIND OR EMPLACE 55 | // eslint-disable-next-line 56 | let mit: HashMap.Iterator< 57 | WebSocketAcceptor, 58 | SolidComponent.LocalArea 59 | > = this.local_areas_.find(resolver.acceptor); 60 | if (mit.equals(this.local_areas_.end()) === true) 61 | mit = this.local_areas_.emplace(resolver.acceptor, { 62 | queue: new List(), 63 | aggregate: resolver.aggregate, 64 | }).first; 65 | else { 66 | for (const key in resolver.aggregate) 67 | (mit.second.aggregate as Record)[key] += 68 | resolver.aggregate[key]; 69 | resolver.aggregate = mit.second.aggregate; 70 | } 71 | 72 | // INSERT NEW ITEM 73 | const lit: List.Iterator = mit.second.queue.insert( 74 | mit.second.queue.end(), 75 | resolver, 76 | ); 77 | 78 | //---- 79 | // GLOBAL QUEUE 80 | //---- 81 | const ret: List.Iterator = this.queue_.insert( 82 | this.queue_.end(), 83 | resolver, 84 | ); 85 | resolver.iterator = ret; 86 | resolver.destructor = () => { 87 | resolver.handler = null; 88 | resolver.disolver.value = undefined; 89 | 90 | if (resolver.disolver.erased_ !== true) 91 | resolver.disolver.source().erase(resolver.disolver); 92 | return mit.second.queue.erase(lit); 93 | }; 94 | return ret; 95 | } 96 | 97 | protected _Get_local_area( 98 | acceptor: WebSocketAcceptor, 99 | ): SolidComponent.LocalArea | null { 100 | const it: HashMap.Iterator< 101 | WebSocketAcceptor, 102 | SolidComponent.LocalArea 103 | > = this.local_areas_.find(acceptor); 104 | return it.equals(this.local_areas_.end()) === false ? it.second : null; 105 | } 106 | } 107 | 108 | /** 109 | * @internal 110 | */ 111 | export namespace SolidComponent { 112 | export interface Resolver< 113 | T extends Resolver, 114 | AggregateT extends Record, 115 | > { 116 | // THREAD HANDLER 117 | handler: Function | null; 118 | lockType: LockType; 119 | 120 | // ASSET FFOR CLIENT 121 | acceptor: WebSocketAcceptor; 122 | aggregate: AggregateT; 123 | disolver: Disolver; 124 | 125 | // FOR DESTRUCTION 126 | destructor?: () => List.Iterator; 127 | iterator?: List.Iterator; 128 | } 129 | 130 | export type LocalArea< 131 | ResolverT extends Resolver, 132 | AggregateT extends Record, 133 | > = { 134 | queue: List; 135 | aggregate: AggregateT; 136 | }; 137 | } 138 | -------------------------------------------------------------------------------- /src/server/components/internal/Disolver.ts: -------------------------------------------------------------------------------- 1 | import { List } from "tstl"; 2 | import { Joiner } from "./Joiner"; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export type Disolver = List.Iterator & { erased_?: boolean }; 8 | -------------------------------------------------------------------------------- /src/server/components/internal/Joiner.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @internal 3 | */ 4 | export type Joiner = (() => void) | (() => Promise) | undefined; 5 | -------------------------------------------------------------------------------- /src/server/global/GlobalBarriers.ts: -------------------------------------------------------------------------------- 1 | import { GlobalBase } from "./GlobalBase"; 2 | import { ServerBarrier } from "../components/ServerBarrier"; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export class GlobalBarriers extends GlobalBase { 8 | protected _Create_component(size: number): ServerBarrier { 9 | return new ServerBarrier(size); 10 | } 11 | 12 | protected _Returns({}: ServerBarrier): void {} 13 | } 14 | -------------------------------------------------------------------------------- /src/server/global/GlobalBase.ts: -------------------------------------------------------------------------------- 1 | import { WebSocketAcceptor } from "tgrid"; 2 | import { HashMap } from "tstl"; 3 | 4 | import { IComponent } from "../components/IComponent"; 5 | import { ProviderGroup } from "../ProviderGroup"; 6 | 7 | /** 8 | * @internal 9 | */ 10 | export abstract class GlobalBase { 11 | private dict_: HashMap = new HashMap(); 12 | 13 | /* --------------------------------------------------------- 14 | CONSTRUCTORS 15 | --------------------------------------------------------- */ 16 | protected abstract _Create_component(param: Param): ComponentT; 17 | protected abstract _Returns(component: ComponentT): Ret; 18 | 19 | public emplace( 20 | name: string, 21 | param: Param, 22 | acceptor: WebSocketAcceptor, 23 | ): Ret { 24 | // eslint-disable-next-line 25 | const component: ComponentT = this.dict_.take(name, () => 26 | this._Create_component(param), 27 | ); 28 | component._Insert_acceptor(acceptor); 29 | return this._Returns(component); 30 | } 31 | 32 | public erase( 33 | name: string, 34 | acceptor: WebSocketAcceptor, 35 | ): void { 36 | if (this.dict_.get(name)._Erase_acceptor(acceptor) === true) 37 | this.dict_.erase(name); 38 | } 39 | 40 | /* --------------------------------------------------------- 41 | ACCESSORS 42 | --------------------------------------------------------- */ 43 | public get(name: string): ComponentT { 44 | return this.dict_.get(name); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/server/global/GlobalConditionVariables.ts: -------------------------------------------------------------------------------- 1 | import { GlobalBase } from "./GlobalBase"; 2 | import { ServerConditionVariable } from "../components/ServerConditionVariable"; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export class GlobalConditionVariablaes extends GlobalBase< 8 | ServerConditionVariable, 9 | undefined, 10 | void 11 | > { 12 | protected _Create_component(): ServerConditionVariable { 13 | return new ServerConditionVariable(); 14 | } 15 | 16 | protected _Returns({}: ServerConditionVariable): void {} 17 | } 18 | -------------------------------------------------------------------------------- /src/server/global/GlobalLatches.ts: -------------------------------------------------------------------------------- 1 | import { GlobalBase } from "./GlobalBase"; 2 | import { ServerLatch } from "../components/ServerLatch"; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export class GlobalLatches extends GlobalBase { 8 | protected _Create_component(size: number): ServerLatch { 9 | return new ServerLatch(size); 10 | } 11 | 12 | protected _Returns({}: ServerLatch): void {} 13 | } 14 | -------------------------------------------------------------------------------- /src/server/global/GlobalMutexes.ts: -------------------------------------------------------------------------------- 1 | import { GlobalBase } from "./GlobalBase"; 2 | import { ServerMutex } from "../components/ServerMutex"; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export class GlobalMutexes extends GlobalBase { 8 | protected _Create_component(): ServerMutex { 9 | return new ServerMutex(); 10 | } 11 | 12 | protected _Returns({}: ServerMutex): void {} 13 | } 14 | -------------------------------------------------------------------------------- /src/server/global/GlobalSemaphores.ts: -------------------------------------------------------------------------------- 1 | import { GlobalBase } from "./GlobalBase"; 2 | import { ServerSemaphore } from "../components/ServerSemaphore"; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export class GlobalSemaphores extends GlobalBase< 8 | ServerSemaphore, 9 | number, 10 | number 11 | > { 12 | protected _Create_component(max: number): ServerSemaphore { 13 | return new ServerSemaphore(max); 14 | } 15 | 16 | protected _Returns(sema: ServerSemaphore): number { 17 | return sema.max(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/server/providers/BarriersProvider.ts: -------------------------------------------------------------------------------- 1 | import { ProviderBase } from "./ProviderBase"; 2 | import { GlobalBarriers } from "../global/GlobalBarriers"; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export class BarriersProvider extends ProviderBase< 8 | GlobalBarriers, 9 | number, 10 | void 11 | > { 12 | public wait(name: string): Promise { 13 | return this.get(name).wait(this.acceptor_, this.createDisolver()); 14 | } 15 | 16 | public wait_for(name: string, ms: number): Promise { 17 | return this.get(name).wait_for(ms, this.acceptor_, this.createDisolver()); 18 | } 19 | 20 | public arrive(name: string, n: number): Promise { 21 | return this.get(name).arrive(n); 22 | } 23 | 24 | public arrive_and_drop(name: string): Promise { 25 | return this.get(name).arrive_and_drop(); 26 | } 27 | 28 | public arrive_and_wait(name: string): Promise { 29 | return this.get(name).arrive_and_wait( 30 | this.acceptor_, 31 | this.createDisolver(), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/server/providers/ConditionVariablesProvider.ts: -------------------------------------------------------------------------------- 1 | import { ProviderBase } from "./ProviderBase"; 2 | import { GlobalConditionVariablaes } from "../global/GlobalConditionVariables"; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export class ConditionVariablesProvider extends ProviderBase< 8 | GlobalConditionVariablaes, 9 | undefined, 10 | void 11 | > { 12 | public wait(name: string): Promise { 13 | return this.get(name).wait(this.acceptor_, this.createDisolver()); 14 | } 15 | 16 | public wait_for(name: string, ms: number): Promise { 17 | return this.get(name).wait_for(ms, this.acceptor_, this.createDisolver()); 18 | } 19 | 20 | public notify_one(name: string): Promise { 21 | return this.get(name).notify_one(); 22 | } 23 | 24 | public notify_all(name: string): Promise { 25 | return this.get(name).notify_all(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/server/providers/LatchesProvider.ts: -------------------------------------------------------------------------------- 1 | import { ProviderBase } from "./ProviderBase"; 2 | import { GlobalLatches } from "../global/GlobalLatches"; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export class LatchesProvider extends ProviderBase { 8 | public wait(name: string): Promise { 9 | return this.get(name).wait(this.acceptor_, this.createDisolver()); 10 | } 11 | 12 | public try_wait(name: string): Promise { 13 | return this.get(name).try_wait(); 14 | } 15 | 16 | public wait_for(name: string, ms: number): Promise { 17 | return this.get(name).wait_for(ms, this.acceptor_, this.createDisolver()); 18 | } 19 | 20 | public count_down(name: string, n: number): Promise { 21 | return this.get(name).count_down(n); 22 | } 23 | 24 | public arrive_and_wait(name: string): Promise { 25 | return this.get(name).arrive_and_wait( 26 | this.acceptor_, 27 | this.createDisolver(), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/server/providers/MutexesProvider.ts: -------------------------------------------------------------------------------- 1 | import { ProviderBase } from "./ProviderBase"; 2 | import { GlobalMutexes } from "../global/GlobalMutexes"; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export class MutexesProvider extends ProviderBase< 8 | GlobalMutexes, 9 | undefined, 10 | void 11 | > { 12 | /* --------------------------------------------------------- 13 | WRITE 14 | --------------------------------------------------------- */ 15 | public lock(name: string): Promise { 16 | return this.get(name).lock(this.acceptor_, this.createDisolver()); 17 | } 18 | 19 | public try_lock(name: string): Promise { 20 | return this.get(name).try_lock(this.acceptor_, this.createDisolver()); 21 | } 22 | 23 | public try_lock_for(name: string, ms: number): Promise { 24 | return this.get(name).try_lock_for( 25 | ms, 26 | this.acceptor_, 27 | this.createDisolver(), 28 | ); 29 | } 30 | 31 | public unlock(name: string): Promise { 32 | return this.get(name).unlock(this.acceptor_); 33 | } 34 | 35 | /* --------------------------------------------------------- 36 | READ 37 | --------------------------------------------------------- */ 38 | public lock_shared(name: string): Promise { 39 | return this.get(name).lock_shared(this.acceptor_, this.createDisolver()); 40 | } 41 | public try_lock_shared(name: string): Promise { 42 | return this.get(name).try_lock_shared( 43 | this.acceptor_, 44 | this.createDisolver(), 45 | ); 46 | } 47 | 48 | public try_lock_shared_for(name: string, ms: number): Promise { 49 | return this.get(name).try_lock_shared_for( 50 | ms, 51 | this.acceptor_, 52 | this.createDisolver(), 53 | ); 54 | } 55 | 56 | public unlock_shared(name: string): Promise { 57 | return this.get(name).unlock_shared(this.acceptor_); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/server/providers/ProviderBase.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @packageDocumentation 3 | * @module msv 4 | */ 5 | //----------------------------------------------------------- 6 | import { WebSocketAcceptor } from "tgrid"; 7 | import { List, HashSet, OutOfRange } from "tstl"; 8 | 9 | import { GlobalBase } from "../global/GlobalBase"; 10 | import { Joiner } from "../components/internal/Joiner"; 11 | import { ProviderGroup } from "../ProviderGroup"; 12 | 13 | /** 14 | * @internal 15 | */ 16 | export abstract class ProviderBase< 17 | GlobalT extends GlobalBase, 18 | Param, 19 | Ret, 20 | > { 21 | protected readonly global_: GlobalT; 22 | protected readonly acceptor_: WebSocketAcceptor; 23 | private readonly disolvers_: List; 24 | 25 | private readonly names_: HashSet; 26 | private readonly remote_name_: string; 27 | 28 | /* --------------------------------------------------------- 29 | CONSTRUCTORS 30 | --------------------------------------------------------- */ 31 | public constructor( 32 | global: GlobalT, 33 | acceptor: WebSocketAcceptor, 34 | disolvers: List, 35 | remoteName: string, 36 | ) { 37 | this.global_ = global; 38 | this.acceptor_ = acceptor; 39 | this.disolvers_ = disolvers; 40 | 41 | this.names_ = new HashSet(); 42 | this.remote_name_ = remoteName; 43 | } 44 | 45 | public destructor(): void { 46 | for (const name of this.names_) this.erase(name); 47 | } 48 | 49 | protected createDisolver(): List.Iterator { 50 | return this.disolvers_.insert(this.disolvers_.end(), undefined); 51 | } 52 | 53 | /* --------------------------------------------------------- 54 | ACCESSORS 55 | --------------------------------------------------------- */ 56 | public emplace(name: string, param: Param): Ret { 57 | this.names_.insert(name); 58 | return this.global_.emplace(name, param, this.acceptor_); 59 | } 60 | 61 | public erase(name: string): void { 62 | this.names_.erase(name); 63 | this.global_.erase(name, this.acceptor_); 64 | } 65 | 66 | public get( 67 | name: string, 68 | ): GlobalT extends GlobalBase 69 | ? ComponentT 70 | : unknown { 71 | if (this.names_.has(name) === false) 72 | throw new OutOfRange( 73 | `Error on ${this.remote_name_}: you've already erased the component.`, 74 | ); 75 | 76 | return this.global_.get(name); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/server/providers/SemaphoresProvider.ts: -------------------------------------------------------------------------------- 1 | import { ProviderBase } from "./ProviderBase"; 2 | import { GlobalSemaphores } from "../global/GlobalSemaphores"; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export class SemaphoresProvider extends ProviderBase< 8 | GlobalSemaphores, 9 | number, 10 | number 11 | > { 12 | public acquire(name: string): Promise { 13 | return this.get(name).acquire(this.acceptor_, this.createDisolver()); 14 | } 15 | 16 | public try_acquire(name: string): Promise { 17 | return this.get(name).try_acquire(this.acceptor_, this.createDisolver()); 18 | } 19 | 20 | public try_acquire_for(name: string, ms: number): Promise { 21 | return this.get(name).try_acquire_for( 22 | ms, 23 | this.acceptor_, 24 | this.createDisolver(), 25 | ); 26 | } 27 | 28 | public release(name: string, n: number): Promise { 29 | return this.get(name).release(n, this.acceptor_); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/condition_variables/test_condition_variable_disconnections.ts: -------------------------------------------------------------------------------- 1 | import { IPointer, sleep_for } from "tstl"; 2 | import { ConnectionFactory } from "../internal/ConnectionFactory"; 3 | 4 | async function wait_and_disconnect( 5 | factory: ConnectionFactory, 6 | ptr: IPointer, 7 | ): Promise { 8 | let connector = await factory(); 9 | let cv = await connector.getConditionVariable( 10 | "test_condition_variable_disconnections", 11 | ); 12 | 13 | cv.wait() 14 | .then(() => ++ptr.value) 15 | .catch(() => {}); 16 | await sleep_for(50); 17 | await connector.close(); 18 | } 19 | 20 | export async function test_condition_variable_disconnections( 21 | factory: ConnectionFactory, 22 | ): Promise { 23 | let connector = await factory(); 24 | let cv = await connector.getConditionVariable( 25 | "test_condition_variable_disconnections", 26 | ); 27 | let ptr: IPointer = { value: 0 }; 28 | 29 | let promises: Promise[] = []; 30 | for (let i: number = 0; i < 4; ++i) 31 | promises.push(wait_and_disconnect(factory, ptr)); 32 | await Promise.all(promises); 33 | 34 | cv.wait().then(() => ++ptr.value); 35 | await cv.notify_all(); 36 | await sleep_for(50); 37 | 38 | if (ptr.value !== 1) 39 | throw new Error( 40 | "Error on RemoteConditionVariable.wait(): disconnection does not cancel the wait.", 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /test/condition_variables/test_condition_variable_waits.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionFactory } from "../internal/ConnectionFactory"; 2 | import { sleep_for } from "tstl"; 3 | 4 | const SLEEP_TIME: number = 100; 5 | const WAIT_COUNT: number = 10; 6 | 7 | export async function test_condition_variable_waits( 8 | factory: ConnectionFactory, 9 | ): Promise { 10 | let connector = await factory(); 11 | let cv = await connector.getConditionVariable( 12 | "test_condition_variable_waits", 13 | ); 14 | let wait_count: number = 0; 15 | 16 | //---- 17 | // WAIT & NOTIFY 18 | //---- 19 | // THERE'RE 10 WAITERS; HOLDERS 20 | for (let i: number = 0; i < WAIT_COUNT; ++i) { 21 | cv.wait().then(() => { 22 | --wait_count; 23 | }); 24 | ++wait_count; 25 | } 26 | 27 | // NOTIFY ONE 28 | cv.notify_one(); 29 | await sleep_for(SLEEP_TIME); 30 | 31 | if (wait_count !== WAIT_COUNT - 1) 32 | throw new Error("Bug on ConditionVariable.notify_one()."); 33 | 34 | // NOTIFY ALL 35 | cv.notify_all(); 36 | await sleep_for(SLEEP_TIME); 37 | 38 | if (wait_count !== 0) 39 | throw new Error("Bug on ConditionVariable.notify_all()."); 40 | 41 | //---- 42 | // WAIT FOR & NOTIFY 43 | //---- 44 | let success_count: number = 0; 45 | 46 | // THERE'RE 10 WAITERS, HOLDERS, WITH DIFFERENT TIMES 47 | for (let i: number = 0; i < WAIT_COUNT; ++i) { 48 | cv.wait_for(i * SLEEP_TIME).then((ret) => { 49 | if (ret === true) ++success_count; 50 | }); 51 | } 52 | 53 | // NOTIFY ONE 54 | await cv.notify_one(); 55 | 56 | // NOTIFY ALL WHEN BE HALT TIME 57 | await sleep_for((WAIT_COUNT * SLEEP_TIME) / 2.0); 58 | cv.notify_all(); 59 | 60 | // VALIDATE SUCCESS COUNT 61 | await sleep_for(SLEEP_TIME); 62 | if (success_count < 3 || success_count > 7) 63 | throw new Error( 64 | "Bug on ConditionVariable.wait_for(): it does not work in exact time.", 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { MutexConnector, MutexServer } from "mutex-server"; 3 | 4 | import { IActivation } from "./internal/IActivation"; 5 | import { ConnectionFactory } from "./internal/ConnectionFactory"; 6 | 7 | const EXTENSION = __filename.substr(-2); 8 | if (EXTENSION === "js") require("source-map-support").install(); 9 | 10 | const PORT = 44994; 11 | const URL = `ws://127.0.0.1:${PORT}`; 12 | const HEADER = { password: "some_password" }; 13 | 14 | interface IModule { 15 | [key: string]: ( 16 | factory: ConnectionFactory, 17 | server: MutexServer, 18 | ) => Promise; 19 | } 20 | 21 | async function measure(job: () => Promise): Promise { 22 | let time: number = Date.now(); 23 | await job(); 24 | return Date.now() - time; 25 | } 26 | 27 | async function iterate( 28 | factory: ConnectionFactory, 29 | server: MutexServer, 30 | path: string, 31 | ): Promise { 32 | const fileList: string[] = await fs.promises.readdir(path); 33 | for (let file of fileList) { 34 | const currentPath: string = `${path}/${file}`; 35 | const stats: fs.Stats = await fs.promises.lstat(currentPath); 36 | 37 | if ( 38 | stats.isDirectory() === true && 39 | file !== "internal" && 40 | file !== "manual" 41 | ) { 42 | await iterate(factory, server, currentPath); 43 | continue; 44 | } else if ( 45 | file.substr(-3) !== `.${EXTENSION}` || 46 | currentPath === `${__dirname}/index.${EXTENSION}` 47 | ) 48 | continue; 49 | 50 | const external: IModule = await import( 51 | currentPath.substr(0, currentPath.length - 3) 52 | ); 53 | for (let key in external) { 54 | if (key.substr(0, 5) !== "test_") continue; 55 | else if (process.argv[2] && key.indexOf(process.argv[2]) === -1) continue; 56 | 57 | process.stdout.write(` - ${key}`); 58 | let time: number = await measure(() => external[key](factory, server)); 59 | console.log(`: ${time} ms`); 60 | } 61 | } 62 | } 63 | 64 | async function main(): Promise { 65 | //---- 66 | // PREPARE ASSETS 67 | //---- 68 | // PRINT TITLE 69 | console.log("=========================================================="); 70 | console.log(" Mutex Server - Test Automation Program "); 71 | console.log("=========================================================="); 72 | 73 | // OPEN SERVER 74 | const server: MutexServer = new MutexServer(); 75 | await server.open(PORT, async (acceptor) => { 76 | if (acceptor.header.password === HEADER.password) await acceptor.accept(); 77 | else await acceptor.reject(); 78 | }); 79 | 80 | // CONNECTION-FACTORY TO THE SERVER 81 | let sequence: number = 0; 82 | const connectorList: MutexConnector[] = []; 83 | 84 | const factory: ConnectionFactory = async () => { 85 | const connector: MutexConnector = new MutexConnector({ 86 | uid: ++sequence, 87 | ...HEADER, 88 | }); 89 | await connector.connect(URL); 90 | 91 | connectorList.push(connector); 92 | return connector; 93 | }; 94 | 95 | //---- 96 | // TEST AUTOMATION 97 | //---- 98 | // DO TEST WITH ELAPSED TIME 99 | let time: number = await measure(() => iterate(factory, server, __dirname)); 100 | 101 | // PRINT ELAPSED TIME 102 | console.log("----------------------------------------------------------"); 103 | console.log("Success"); 104 | console.log(` - elapsed time: ${time} ms`); 105 | 106 | // MEMORY USAGE 107 | const memory: NodeJS.MemoryUsage = process.memoryUsage(); 108 | for (const property in memory) { 109 | const amount: number = 110 | memory[property as keyof NodeJS.MemoryUsage] / 10 ** 6; 111 | console.log(` - ${property}: ${amount} MB`); 112 | } 113 | console.log("----------------------------------------------------------\n"); 114 | 115 | //---- 116 | // TERMINATE 117 | //---- 118 | for (const connector of connectorList) 119 | if (connector.state === MutexConnector.State.OPEN) await connector.close(); 120 | 121 | await server.close(); 122 | } 123 | main().catch((exp) => { 124 | process.stdout.write("\n"); 125 | console.log(exp); 126 | process.exit(-1); 127 | }); 128 | -------------------------------------------------------------------------------- /test/internal/ConnectionFactory.ts: -------------------------------------------------------------------------------- 1 | import { MutexConnector } from "mutex-server"; 2 | 3 | import { IActivation } from "./IActivation"; 4 | 5 | export interface ConnectionFactory { 6 | (): Promise>; 7 | } 8 | -------------------------------------------------------------------------------- /test/internal/IActivation.ts: -------------------------------------------------------------------------------- 1 | export interface IActivation { 2 | uid: number; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /test/internal/Validator.ts: -------------------------------------------------------------------------------- 1 | import { sleep_for } from "tstl"; 2 | import { 3 | ILockable, 4 | ITimedLockable, 5 | ISharedLockable, 6 | ISharedTimedLockable, 7 | } from "tstl/lib/base/thread"; 8 | 9 | export namespace Validator { 10 | const SLEEP_TIME = 50; 11 | const READ_COUNT = 10; 12 | 13 | /* --------------------------------------------------------- 14 | WRITE LOCK 15 | --------------------------------------------------------- */ 16 | export async function lock(mutex: ILockable): Promise { 17 | const start: number = Date.now(); 18 | 19 | // LOCK FOR A SECOND 20 | await mutex.lock(); 21 | sleep_for(SLEEP_TIME).then(() => mutex.unlock()); 22 | 23 | // TRY LOCK AGAIN 24 | await mutex.lock(); 25 | const elapsed: number = Date.now() - start; 26 | await mutex.unlock(); 27 | 28 | if (elapsed < SLEEP_TIME * 0.95) 29 | throw new Error( 30 | `Error on ${mutex.constructor.name}.lock() & unlock(): it does not work in exact time.`, 31 | ); 32 | } 33 | 34 | export async function try_lock( 35 | mtx: ITimedLockable, 36 | name: string = mtx.constructor.name, 37 | ): Promise { 38 | const start: number = Date.now(); 39 | 40 | // DO LOCK 41 | let ret: boolean = await mtx.try_lock_for(SLEEP_TIME); 42 | if (ret === false) 43 | throw new Error( 44 | `Bug on ${name}.try_lock_for(): it does not return exact value`, 45 | ); 46 | 47 | // TRY LOCK AGAIN 48 | ret = await mtx.try_lock_for(SLEEP_TIME); 49 | const elapsed: number = Date.now() - start; 50 | 51 | if (ret === true) 52 | throw new Error( 53 | `Bug on ${name}.try_lock_for(): it does not return exact value`, 54 | ); 55 | else if (elapsed < SLEEP_TIME * 0.95) 56 | throw new Error( 57 | `Bug on ${name}.try_lock_for(): it does not work in exact time`, 58 | ); 59 | 60 | await mtx.unlock(); 61 | } 62 | 63 | /* --------------------------------------------------------- 64 | READ LOCK 65 | --------------------------------------------------------- */ 66 | export async function lock_shared( 67 | mtx: ILockable & ISharedLockable, 68 | ): Promise { 69 | //---- 70 | // READ SIMULTANEOUSLY 71 | //---- 72 | // READ LOCK; 10 TIMES 73 | let read_count: number = 0; 74 | for (let i: number = 0; i < READ_COUNT; ++i) { 75 | mtx.lock_shared(); 76 | ++read_count; 77 | } 78 | if (read_count !== READ_COUNT) 79 | // READ LOCK CAN BE DONE SIMULTANEOUSLY 80 | throw new Error( 81 | `Bug on ${mtx.constructor.name}.lock_shared(): it doesn't support the simultaneous lock.`, 82 | ); 83 | 84 | //---- 85 | // READ FIRST, WRITE LATER 86 | //---- 87 | let start_time: number = Date.now(); 88 | sleep_for(SLEEP_TIME).then(() => { 89 | // SLEEP FOR A SECOND AND UNLOCK ALL READINGS 90 | for (let i: number = 0; i < READ_COUNT; ++i) mtx.unlock_shared(); 91 | }); 92 | 93 | // DO WRITE LOCK; MUST BE BLOCKED 94 | await mtx.lock(); 95 | 96 | // VALIDATE ELAPSED TIME 97 | let elapsed_time: number = Date.now() - start_time; 98 | if (elapsed_time < SLEEP_TIME * 0.95) 99 | throw new Error( 100 | `Bug on ${mtx.constructor.name}.lock(): it does not block writing while reading.`, 101 | ); 102 | 103 | //---- 104 | // WRITE FIRST, READ LATER 105 | //---- 106 | start_time = Date.now(); 107 | 108 | // SLEEP FOR A SECOND AND UNLOCK WRITINGS 109 | sleep_for(SLEEP_TIME).then(() => mtx.unlock()); 110 | for (let i: number = 0; i < READ_COUNT; ++i) await mtx.lock_shared(); 111 | 112 | // VALIDATE ELAPSED TIME 113 | elapsed_time = Date.now() - start_time; 114 | if (elapsed_time < SLEEP_TIME * 0.95) 115 | throw new Error( 116 | `Bug on ${mtx.constructor.name}.lock_shared(): it does not block reading while writing.`, 117 | ); 118 | 119 | // RELEASE READING LOCK FOR THE NEXT STEP 120 | for (let i: number = 0; i < READ_COUNT; ++i) await mtx.unlock_shared(); 121 | } 122 | 123 | export async function try_lock_shared( 124 | mtx: ITimedLockable & ISharedTimedLockable, 125 | ): Promise { 126 | let start: number; 127 | let elapsed: number; 128 | let flag: boolean; 129 | 130 | //---- 131 | // READ SIMULTANEOUSLY 132 | //---- 133 | start = Date.now(); 134 | 135 | // READ LOCK; 10 TIMES 136 | for (let i: number = 0; i < READ_COUNT; ++i) { 137 | flag = await mtx.try_lock_shared_for(SLEEP_TIME); 138 | if (flag === false) 139 | throw new Error( 140 | `Bug on ${mtx.constructor.name}.try_lock_shared_for(): it does not return exact value.`, 141 | ); 142 | } 143 | 144 | // VALIDATE ELAPSED TIME 145 | elapsed = Date.now() - start; 146 | if (elapsed >= SLEEP_TIME) 147 | throw new Error( 148 | `Bug on ${mtx.constructor.name}.try_lock_shared_for(): it does not support simultaneous lock.`, 149 | ); 150 | 151 | //---- 152 | // WRITE LOCK 153 | //---- 154 | // TRY WRITE LOCK ON READING 155 | start = Date.now(); 156 | flag = await mtx.try_lock_for(SLEEP_TIME); 157 | elapsed = Date.now() - start; 158 | 159 | if (flag === true) 160 | throw new Error( 161 | `Bug on ${mtx.constructor.name}.try_lock_for(): it does not return exact value while reading.`, 162 | ); 163 | else if (elapsed < SLEEP_TIME * 0.95) 164 | throw new Error( 165 | `Bug on ${mtx.constructor.name}.try_lock_for(): it does not block while reading.`, 166 | ); 167 | 168 | // TRY WRITE LOCK AFTER READING 169 | sleep_for(SLEEP_TIME).then(() => { 170 | for (let i: number = 0; i < READ_COUNT; ++i) mtx.unlock_shared(); 171 | }); 172 | start = Date.now(); 173 | flag = await mtx.try_lock_for(SLEEP_TIME * 1.5); 174 | elapsed = Date.now() - start; 175 | 176 | if (flag === false) 177 | throw new Error( 178 | `Bug on ${mtx.constructor.name}.try_lock_for(): it does not return exact value while reading.`, 179 | ); 180 | else if (elapsed < SLEEP_TIME * 0.95) 181 | throw new Error( 182 | `Bug on ${mtx.constructor.name}.try_lock_for(): it does not work in exact time.`, 183 | ); 184 | 185 | //---- 186 | // READ LOCK 187 | //---- 188 | // READ LOCK ON WRITING 189 | start = Date.now(); 190 | for (let i: number = 0; i < READ_COUNT; ++i) { 191 | flag = await mtx.try_lock_shared_for(SLEEP_TIME); 192 | if (flag === true) 193 | throw new Error( 194 | `Bug on ${mtx.constructor.name}.try_lock_shared_for(): it does not return exact value while writing.`, 195 | ); 196 | } 197 | elapsed = Date.now() - start; 198 | 199 | if (elapsed < SLEEP_TIME * READ_COUNT * 0.95) 200 | throw new Error( 201 | `Bug on ${mtx.constructor.name}.try_lock_shared_for(): it does not work in exact time.`, 202 | ); 203 | 204 | // READ LOCK AFTER WRITING 205 | start = Date.now(); 206 | sleep_for(SLEEP_TIME).then(() => mtx.unlock()); 207 | 208 | for (let i: number = 0; i < READ_COUNT; ++i) { 209 | flag = await mtx.try_lock_shared_for(SLEEP_TIME * 1.5); 210 | if (flag === false) 211 | throw new Error( 212 | `Bug on ${mtx.constructor.name}.try_lock_shared_for(): it does not return exact value after writing.`, 213 | ); 214 | } 215 | elapsed = Date.now() - start; 216 | 217 | if (elapsed < SLEEP_TIME * 0.95 || elapsed >= SLEEP_TIME * 5.0) 218 | throw new Error( 219 | `Bug on ${mtx.constructor.name}.try_lock_shared_for(): it does not work in exact time.`, 220 | ); 221 | 222 | // RELEASE READING LOCK FOR THE NEXT STEP 223 | for (let i: number = 0; i < READ_COUNT; ++i) await mtx.unlock_shared(); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /test/manual/remote-mutex.ts: -------------------------------------------------------------------------------- 1 | import msv, { MutexServer } from "mutex-server"; 2 | import std from "tstl"; 3 | 4 | const PASSWORD = "qweqwe123!"; 5 | const PORT = 37119; 6 | 7 | async function client(index: number, character: string): Promise { 8 | // CONNECT TO THE SERVER 9 | const connector: msv.MutexConnector = new msv.MutexConnector( 10 | PASSWORD, 11 | ); 12 | await connector.connect(`ws://127.0.0.1:${PORT}`); 13 | 14 | // GET LOCK 15 | const mutex: msv.RemoteMutex = await connector.getMutex("printer"); 16 | await mutex.lock(); 17 | 18 | // PRINTS A LINE VERY SLOWLY MONOPOLYING THE MUTEX 19 | process.stdout.write(`Connector #${index} is monopolying a mutex: `); 20 | for (let i: number = 0; i < 20; ++i) { 21 | process.stdout.write(character); 22 | await std.sleep_for(50); 23 | } 24 | process.stdout.write("\n"); 25 | 26 | // ALTHOUGH THE CLIENT DOES NOT RELEASE THE LOCK 27 | if (Math.random() < 0.5) await mutex.unlock(); 28 | // SERVER WILL UNLOCK IT AUTOMATICALLY AFTER THE DISCONNECTION 29 | else await connector.close(); 30 | } 31 | 32 | async function main(): Promise { 33 | // OPEN SERVER 34 | const server: MutexServer = new MutexServer(); 35 | await server.open(PORT, async (acceptor) => { 36 | if (acceptor.header === PASSWORD) await acceptor.accept(); 37 | else await acceptor.reject(); 38 | }); 39 | 40 | // CREATE 10 CLIENTS LOCKING MUTEX 41 | const promises: Promise[] = []; 42 | for (let i: number = 0; i < 4; ++i) { 43 | let character: string = std.randint(0, 9).toString(); 44 | promises.push(client(i + 1, character)); 45 | } 46 | 47 | // WAIT THE CLIENTS TO BE DISCONNCTED AND CLOSE SERVER 48 | await Promise.all(promises); 49 | await server.close(); 50 | } 51 | main(); 52 | -------------------------------------------------------------------------------- /test/mutextes/test_mutex_disconnections.ts: -------------------------------------------------------------------------------- 1 | import { MutexConnector, RemoteMutex } from "mutex-server"; 2 | import { sleep_for } from "tstl"; 3 | 4 | import { IActivation } from "../internal/IActivation"; 5 | import { ConnectionFactory } from "../internal/ConnectionFactory"; 6 | 7 | async function test_disconnection( 8 | factory: ConnectionFactory, 9 | index: number, 10 | ): Promise { 11 | const connector: MutexConnector = await factory(); 12 | const mutex: RemoteMutex = await connector.getMutex( 13 | "test_mutex_disconnection", 14 | ); 15 | 16 | if (index % 2 === 0) { 17 | if ((await mutex.try_lock_for(SLEEP * COUNT * 20)) === false) 18 | throw new Error( 19 | "Error on RemoteMutex.try_lock_for(): disconnected clients do not return their acquisitions.", 20 | ); 21 | } else mutex.try_lock_for(SLEEP * COUNT * 20).catch(() => {}); 22 | 23 | await sleep_for(SLEEP * index); 24 | await connector.close(); 25 | } 26 | 27 | export async function test_mutex_disconnections( 28 | factory: ConnectionFactory, 29 | ): Promise { 30 | const promises: Promise[] = []; 31 | for (let i: number = 0; i < COUNT; ++i) 32 | promises.push(test_disconnection(factory, i)); 33 | 34 | await Promise.all(promises); 35 | } 36 | 37 | const COUNT = 4; 38 | const SLEEP = 10; 39 | -------------------------------------------------------------------------------- /test/mutextes/test_mutex_locks.ts: -------------------------------------------------------------------------------- 1 | import { MutexConnector, RemoteMutex } from "mutex-server"; 2 | import { Pair, sleep_for } from "tstl"; 3 | 4 | import { IActivation } from "../internal/IActivation"; 5 | import { ConnectionFactory } from "../internal/ConnectionFactory"; 6 | import { Validator } from "../internal/Validator"; 7 | 8 | const enum Status { 9 | START_READING = "Start Reading", 10 | END_READING = "End Reading", 11 | START_WRITING = "Start Writing", 12 | END_WRITING = "End Writing", 13 | } 14 | const MAGNIFIER: number = 3; 15 | 16 | export async function test_mutex_locks( 17 | factory: ConnectionFactory, 18 | ): Promise { 19 | const connector: MutexConnector = await factory(); 20 | const mutex: RemoteMutex = await connector.getMutex("remote_mutex"); 21 | 22 | // TEST COMMON FEATURES 23 | await Validator.lock(mutex); 24 | await Validator.try_lock(mutex); 25 | await Validator.lock_shared(mutex); 26 | await Validator.try_lock_shared(mutex); 27 | 28 | // @todo: must be removed 29 | if (1 === 1) return; 30 | 31 | // TEST SPECIAL FEATURES 32 | const statusList: Pair[] = []; 33 | 34 | try { 35 | const promises: Promise[] = []; 36 | for (let i: number = 0; i < 25; ++i) promises.push(read(mutex, statusList)); 37 | promises.push(write(mutex, statusList)); 38 | 39 | await Promise.all(promises); 40 | 41 | let reading: number = 0; 42 | let writing: number = 0; 43 | 44 | for (let i: number = 0; i < statusList.length; ++i) { 45 | const status: Status = statusList[i].first; 46 | 47 | if (status === Status.START_READING) ++reading; 48 | else if (status === Status.START_WRITING) ++writing; 49 | else if (status === Status.END_READING) --reading; 50 | else --writing; 51 | 52 | if (writing > 0 && reading > 0) 53 | throw new Error( 54 | `Bug on SharedTimeMutex; reading and writing at the same time at ${i}`, 55 | ); 56 | } 57 | } catch (exp) { 58 | for (let pair of statusList) console.log(pair.first, pair.second); 59 | throw exp; 60 | } 61 | } 62 | 63 | async function write( 64 | mutex: RemoteMutex, 65 | statusList: Pair[], 66 | ): Promise { 67 | for (let i: number = 0; i < MAGNIFIER * 10; ++i) { 68 | // JUST DELAY FOR SAFETY 69 | await sleep_for(100); 70 | const time: number = Date.now(); 71 | 72 | // DO WRITE 73 | await mutex.lock(); 74 | { 75 | const now: number = Date.now(); 76 | statusList.push(new Pair(Status.START_WRITING, now - time)); 77 | 78 | await sleep_for(50); 79 | statusList.push(new Pair(Status.END_WRITING, Date.now() - now)); 80 | } 81 | await mutex.unlock(); 82 | } 83 | } 84 | 85 | async function read( 86 | mutex: RemoteMutex, 87 | statusList: Pair[], 88 | ): Promise { 89 | for (let i: number = 0; i < MAGNIFIER * 100; ++i) { 90 | const time: number = Date.now(); 91 | 92 | // DO READ 93 | await mutex.lock_shared(); 94 | { 95 | const now: number = Date.now(); 96 | statusList.push(new Pair(Status.START_READING, now - time)); 97 | 98 | await sleep_for(10); 99 | statusList.push(new Pair(Status.END_READING, Date.now() - now)); 100 | } 101 | await mutex.unlock_shared(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /test/semaphores/test_semaphore_acquires.ts: -------------------------------------------------------------------------------- 1 | import { MutexConnector, RemoteSemaphore } from "mutex-server"; 2 | import { sleep_for } from "tstl"; 3 | 4 | import { ConnectionFactory } from "../internal/ConnectionFactory"; 5 | import { IActivation } from "../internal/IActivation"; 6 | import { Validator } from "../internal/Validator"; 7 | import { ITimedLockable } from "tstl/lib/base/thread/ITimedLockable"; 8 | 9 | const SIZE = 8; 10 | 11 | export async function test_semaphore_acquires( 12 | factory: ConnectionFactory, 13 | ): Promise { 14 | //---- 15 | // TEST MUTEX FEATURES 16 | //---- 17 | const connector: MutexConnector = await factory(); 18 | const mutex: RemoteSemaphore = await connector.getSemaphore( 19 | "test_semaphore_acquires_binary", 20 | 1, 21 | ); 22 | const wrapper: ITimedLockable = RemoteSemaphore.get_lockable(mutex); 23 | 24 | await Validator.lock(wrapper); 25 | await Validator.try_lock(wrapper); 26 | 27 | //---- 28 | // TEST SPECIAL FEATURES OF SEMAPHORE 29 | //---- 30 | const semaphore: RemoteSemaphore = await connector.getSemaphore( 31 | "test_semaphore_acquires_counting", 32 | SIZE, 33 | ); 34 | 35 | await _Test_semaphore(semaphore); 36 | await _Test_timed_semaphore(semaphore); 37 | } 38 | 39 | async function _Test_semaphore(s: RemoteSemaphore): Promise { 40 | let acquired_count: number = 0; 41 | 42 | // LOCK 4 TIMES 43 | for (let i: number = 0; i < (await s.max()); ++i) { 44 | await s.acquire(); 45 | ++acquired_count; 46 | } 47 | if (acquired_count !== (await s.max())) 48 | throw new Error(`Bug on Semaphore.acquire()`); 49 | else if ((await s.try_acquire()) === true) 50 | throw new Error(`Bug on Semaphore.try_acquire()`); 51 | 52 | // LOCK 4 TIMES AGAIN -> THEY SHOULD BE HOLD 53 | for (let i: number = 0; i < (await s.max()); ++i) 54 | s.acquire().then(() => { 55 | ++acquired_count; 56 | }); 57 | if (acquired_count !== (await s.max())) 58 | throw new Error(`Bug on Semaphore.acquire() when Semaphore is full`); 59 | 60 | // DO UNLOCK 61 | await s.release(await s.max()); 62 | 63 | if (acquired_count !== 2 * (await s.max())) 64 | throw new Error(`Bug on Semaphore.release()`); 65 | 66 | // RELEASE UNRESOLVED LOCKS 67 | await sleep_for(0); 68 | await s.release(await s.max()); 69 | } 70 | 71 | async function _Test_timed_semaphore(ts: RemoteSemaphore): Promise { 72 | // TRY LOCK FIRST 73 | for (let i: number = 0; i < (await ts.max()); ++i) { 74 | const flag: boolean = await ts.try_acquire_for(0); 75 | if (flag === false) 76 | throw new Error( 77 | "Bug on TimedSemaphore.try_lock_for(); failed to lock when clear", 78 | ); 79 | } 80 | 81 | // TRY LOCK FOR -> MUST BE FAILED 82 | if ((await ts.try_acquire_for(50)) === true) 83 | throw new Error( 84 | "Bug on TimedSemaphore.try_lock_for(); succeeded to lock when must be failed.", 85 | ); 86 | 87 | // LOCK WOULD BE HOLD 88 | let cnt: number = 0; 89 | for (let i: number = 0; i < (await ts.max()) / 2; ++i) 90 | ts.acquire().then(() => { 91 | ++cnt; 92 | }); 93 | 94 | await sleep_for(100); 95 | if (cnt === (await ts.max()) / 2) 96 | throw new Error( 97 | "Bug on TimedSemaphore.try_lock_for(); failed to release holdings.", 98 | ); 99 | 100 | // RELEASE AND LOCK 101 | await ts.release(await ts.max()); 102 | 103 | for (let i: number = 0; i < (await ts.max()) / 2; ++i) { 104 | const flag: boolean = await ts.try_acquire_for(100); 105 | if (flag === false) 106 | throw new Error( 107 | "Bug on TimedSemaphore.try_lock_for(); failed to lock when released.", 108 | ); 109 | } 110 | await ts.release(await ts.max()); 111 | } 112 | -------------------------------------------------------------------------------- /test/semaphores/test_semaphore_disconnections.ts: -------------------------------------------------------------------------------- 1 | import { MutexConnector, RemoteSemaphore } from "mutex-server"; 2 | import { ConnectionFactory } from "../internal/ConnectionFactory"; 3 | import { IActivation } from "../internal/IActivation"; 4 | import { sleep_for } from "tstl"; 5 | 6 | const MAX = 8; 7 | const SLEEP = 50; 8 | 9 | async function acquire_and_disconnect( 10 | factory: ConnectionFactory, 11 | ms: number, 12 | ): Promise { 13 | const connector: MutexConnector = await factory(); 14 | const semaphore: RemoteSemaphore = await connector.getSemaphore( 15 | "test_semaphore_disconnections", 16 | 0, 17 | ); 18 | 19 | if ((await semaphore.max()) !== MAX) 20 | throw new Error("Error on RemoteSemaphore.max(): invalid value."); 21 | else if ((await semaphore.try_acquire()) === false) 22 | throw new Error( 23 | "Error on RemoteSemaphore.try_acquire(): must be true but returns false.", 24 | ); 25 | 26 | sleep_for(ms).then(() => connector.close()); 27 | } 28 | 29 | export async function test_semaphore_disconnections( 30 | factory: ConnectionFactory, 31 | ): Promise { 32 | const connector: MutexConnector = await factory(); 33 | const semaphore: RemoteSemaphore = await connector.getSemaphore( 34 | "test_semaphore_disconnections", 35 | MAX, 36 | ); 37 | 38 | for (let i: number = 0; i < MAX / 2; ++i) 39 | if ((await semaphore.try_acquire()) === false) 40 | throw new Error( 41 | "Error on RemoteSemaphore.try_acquire(): must be true but returns false.", 42 | ); 43 | 44 | for (let i: number = 0; i < MAX / 2; ++i) 45 | await acquire_and_disconnect(factory, SLEEP); 46 | 47 | if ((await semaphore.try_acquire()) === true) 48 | throw new Error( 49 | "Error on RemoteSemaphore.try_acquire(): must be false but returns true.", 50 | ); 51 | 52 | const promises: Promise[] = []; 53 | let count: number = 0; 54 | for (let i: number = 0; i < MAX / 2; ++i) 55 | promises.push( 56 | semaphore.try_acquire_for(SLEEP * 1.2).then(() => { 57 | ++count; 58 | }), 59 | ); 60 | if (count !== 0) 61 | throw new Error( 62 | "Error on RemoteSemaphore.try_acquire_for(): succeded too early.", 63 | ); 64 | 65 | await Promise.all(promises); 66 | if (count !== MAX / 2) 67 | throw new Error( 68 | "Error on RemoteSemaphore.try_acquire_for(): must be succeeded but failed.", 69 | ); 70 | 71 | await semaphore.release(4); 72 | } 73 | -------------------------------------------------------------------------------- /test/semaphores/test_semaphore_simple.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionFactory } from "../internal/ConnectionFactory"; 2 | 3 | export async function test_semaphore_simple( 4 | factory: ConnectionFactory, 5 | ): Promise { 6 | let connector = await factory(); 7 | let semaphore = await connector.getSemaphore("test_sema2", 4); 8 | 9 | for (let i: number = 0; i < 4; ++i) await semaphore.acquire(); 10 | 11 | let promises: Promise[] = []; 12 | let count: number = 0; 13 | 14 | for (let i: number = 0; i < 4; ++i) 15 | promises.push( 16 | semaphore.acquire().then(() => { 17 | ++count; 18 | }), 19 | ); 20 | 21 | await semaphore.release(4); 22 | await Promise.all(promises); 23 | 24 | if (count !== 4) throw new Error("Error on RemoteSemaphore.release(4)"); 25 | } 26 | -------------------------------------------------------------------------------- /test/test_destructors.ts: -------------------------------------------------------------------------------- 1 | import { sleep_for } from "tstl"; 2 | import { MutexConnector, MutexServer, RemoteMutex } from "mutex-server"; 3 | import { IActivation } from "./internal/IActivation"; 4 | import { ConnectionFactory } from "./internal/ConnectionFactory"; 5 | 6 | async function test(factory: ConnectionFactory): Promise { 7 | let connector: MutexConnector = await factory(); 8 | let mutex: RemoteMutex = await connector.getMutex("test_destructors"); 9 | 10 | await mutex.lock_shared(); 11 | await sleep_for(50); 12 | 13 | // mutex.unlock_shared() would be automatically called 14 | await connector.close(); 15 | } 16 | 17 | export async function test_destructors( 18 | factory: ConnectionFactory, 19 | server: MutexServer, 20 | ): Promise { 21 | const promises: Promise[] = []; 22 | for (let i: number = 0; i < 4; ++i) promises.push(test(factory)); 23 | 24 | await Promise.all(promises); 25 | await sleep_for(50); 26 | 27 | if (server["components_"].mutexes["dict_"].has("test_destructors") === true) 28 | throw new Error("Destructor is not working."); 29 | } 30 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../bin", 5 | "paths": { 6 | "mutex-server": ["../src"], 7 | "mutex-server/lib/*": ["../src/*"], 8 | }, 9 | "plugins": [ 10 | { "transform": "typescript-transform-paths" }, 11 | ], 12 | }, 13 | "include": [ 14 | "./", 15 | "../src", 16 | ], 17 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 7 | "lib": [ 8 | "DOM", 9 | "ES2015" 10 | ], /* Specify library files to be included in the compilation. */ 11 | // "allowJs": true, /* Allow javascript files to be compiled. */ 12 | // "checkJs": true, /* Report errors in .js files. */ 13 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 14 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 15 | "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 16 | "sourceMap": true, /* Generates corresponding '.map' file. */ 17 | // "outFile": "./", /* Concatenate and emit output to single file. */ 18 | "outDir": "./lib", /* Redirect output structure to the directory. */ 19 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 20 | // "composite": true, /* Enable project compilation */ 21 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 22 | // "removeComments": true, /* Do not emit comments to output. */ 23 | // "noEmit": true, /* Do not emit outputs. */ 24 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 25 | "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 26 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 27 | 28 | /* Strict Type-Checking Options */ 29 | "strict": true, /* Enable all strict type-checking options. */ 30 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 31 | // "strictNullChecks": true, /* Enable strict null checks. */ 32 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 33 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 34 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 35 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 36 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 37 | 38 | /* Additional Checks */ 39 | "noUnusedLocals": true, /* Report errors on unused locals. */ 40 | "noUnusedParameters": true, /* Report errors on unused parameters. */ 41 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 42 | "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 43 | 44 | /* Module Resolution Options */ 45 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 49 | // "typeRoots": [], /* List of folders to include type definitions from. */ 50 | // "types": [], /* Type declaration files to be included in compilation. */ 51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 52 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 54 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 55 | 56 | /* Source Map Options */ 57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 61 | 62 | /* Experimental Options */ 63 | "stripInternal": true, 64 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 65 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 66 | 67 | /* Advanced Options */ 68 | "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ 69 | }, 70 | "include": ["./src"] 71 | } 72 | --------------------------------------------------------------------------------