From fd8d44e4ce753f65be811694b64f25c372194a9a Mon Sep 17 00:00:00 2001 From: geyun <1556375442@qq.com> Date: Wed, 17 Dec 2025 14:14:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B2=A1=E7=BB=91=E5=AE=9A=E8=BF=87=E6=89=8B?= =?UTF-8?q?=E6=9C=BA=E5=8F=B7=E7=9A=84=20=E5=85=81=E8=AE=B8=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E5=8D=81=E6=AC=A1=EF=BC=8C=E5=BC=B9=E5=87=BA=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E7=A0=81=E5=BC=B9=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 160 +++--- middle.html | 76 ++- node_modules/.package-lock.json | 9 + node_modules/async-mutex/LICENSE | 21 + node_modules/async-mutex/README.md | 540 ++++++++++++++++++ node_modules/async-mutex/es6/Mutex.js | 41 ++ .../async-mutex/es6/MutexInterface.js | 1 + node_modules/async-mutex/es6/Semaphore.js | 153 +++++ .../async-mutex/es6/SemaphoreInterface.js | 1 + node_modules/async-mutex/es6/errors.js | 3 + node_modules/async-mutex/es6/index.js | 5 + node_modules/async-mutex/es6/tryAcquire.js | 8 + node_modules/async-mutex/es6/withTimeout.js | 124 ++++ node_modules/async-mutex/index.mjs | 291 ++++++++++ node_modules/async-mutex/lib/Mutex.d.ts | 12 + node_modules/async-mutex/lib/Mutex.js | 43 ++ .../async-mutex/lib/MutexInterface.d.ts | 17 + .../async-mutex/lib/MutexInterface.js | 2 + node_modules/async-mutex/lib/Semaphore.d.ts | 22 + node_modules/async-mutex/lib/Semaphore.js | 155 +++++ .../async-mutex/lib/SemaphoreInterface.d.ts | 19 + .../async-mutex/lib/SemaphoreInterface.js | 2 + node_modules/async-mutex/lib/errors.d.ts | 3 + node_modules/async-mutex/lib/errors.js | 6 + node_modules/async-mutex/lib/index.d.ts | 7 + node_modules/async-mutex/lib/index.js | 13 + node_modules/async-mutex/lib/tryAcquire.d.ts | 4 + node_modules/async-mutex/lib/tryAcquire.js | 12 + node_modules/async-mutex/lib/withTimeout.d.ts | 4 + node_modules/async-mutex/lib/withTimeout.js | 128 +++++ node_modules/async-mutex/package.json | 88 +++ package-lock.json | 18 + package.json | 1 + server2.js | 89 ++- 34 files changed, 1998 insertions(+), 80 deletions(-) create mode 100644 node_modules/async-mutex/LICENSE create mode 100644 node_modules/async-mutex/README.md create mode 100644 node_modules/async-mutex/es6/Mutex.js create mode 100644 node_modules/async-mutex/es6/MutexInterface.js create mode 100644 node_modules/async-mutex/es6/Semaphore.js create mode 100644 node_modules/async-mutex/es6/SemaphoreInterface.js create mode 100644 node_modules/async-mutex/es6/errors.js create mode 100644 node_modules/async-mutex/es6/index.js create mode 100644 node_modules/async-mutex/es6/tryAcquire.js create mode 100644 node_modules/async-mutex/es6/withTimeout.js create mode 100644 node_modules/async-mutex/index.mjs create mode 100644 node_modules/async-mutex/lib/Mutex.d.ts create mode 100644 node_modules/async-mutex/lib/Mutex.js create mode 100644 node_modules/async-mutex/lib/MutexInterface.d.ts create mode 100644 node_modules/async-mutex/lib/MutexInterface.js create mode 100644 node_modules/async-mutex/lib/Semaphore.d.ts create mode 100644 node_modules/async-mutex/lib/Semaphore.js create mode 100644 node_modules/async-mutex/lib/SemaphoreInterface.d.ts create mode 100644 node_modules/async-mutex/lib/SemaphoreInterface.js create mode 100644 node_modules/async-mutex/lib/errors.d.ts create mode 100644 node_modules/async-mutex/lib/errors.js create mode 100644 node_modules/async-mutex/lib/index.d.ts create mode 100644 node_modules/async-mutex/lib/index.js create mode 100644 node_modules/async-mutex/lib/tryAcquire.d.ts create mode 100644 node_modules/async-mutex/lib/tryAcquire.js create mode 100644 node_modules/async-mutex/lib/withTimeout.d.ts create mode 100644 node_modules/async-mutex/lib/withTimeout.js create mode 100644 node_modules/async-mutex/package.json diff --git a/index.html b/index.html index 73bb950..9c849b3 100755 --- a/index.html +++ b/index.html @@ -7,12 +7,11 @@ - - + + - sata光伏智能体 + 飒沓光伏智能体 @@ -1745,10 +1744,10 @@ if (document.querySelector('.popbox .loading-box')) { document.querySelector('.popbox .loading-box').classList.add('none'); } - userId = res.userId; - setCookie('token', res.token); + // userId = res.userId; + // setCookie('token', res.token); unshowpop(); - getSessionList(); + // getSessionList(); } // } else { @@ -2408,6 +2407,8 @@ }) return null; }); + + console.log(res, 7665) if (!res) { @@ -2423,6 +2424,24 @@ return null; } + if (res.status === 400) { + clearInterval(interval); + if (controller) { + controller.abort(); + controller = null; + } + if (sendBtnImg.classList.contains("none")) { + sendBtnImg.classList.remove('none'); + + } + stopBtn.forEach(e => { + if (!e.classList.contains("none")) { + e.classList.add('none') + } + }) + showpop(); + return; + } sendParam.imageBase64 = ''; @@ -2478,6 +2497,28 @@ }) // renderChatList(); msgDiv.innerHTML = marked.parse(aiMsg.content); + // const shareSpan = document.createElement("span"); + // shareSpan.style.display = "inline-flex"; + // shareSpan.style.alignItems = "center"; + // shareSpan.style.cursor = "pointer"; + // shareSpan.style.marginTop = "8px"; + // shareSpan.style.opacity = "0.75"; + // shareSpan.style.transition = "opacity .2s"; + // shareSpan.title = "分享"; + + // shareSpan.onmouseenter = () => shareSpan.style.opacity = "1"; + // shareSpan.onmouseleave = () => shareSpan.style.opacity = "0.75"; + + // shareSpan.innerHTML = ` + // + // `; + + // shareSpan.onclick = () => { + // generatePoster(aiMsg.content); + // }; + + // if (role === "assistant") { + // 分享按钮 const shareSpan = document.createElement("span"); shareSpan.style.display = "inline-flex"; shareSpan.style.alignItems = "center"; @@ -2491,74 +2532,49 @@ shareSpan.onmouseleave = () => shareSpan.style.opacity = "0.75"; shareSpan.innerHTML = ` - + `; + shareSpan.onclick = () => { - generatePoster(aiMsg.content); + generatePoster(content, imgUrl); }; - if (role === "assistant") { - // 分享按钮 - const shareSpan = document.createElement("span"); - shareSpan.style.display = "inline-flex"; - shareSpan.style.alignItems = "center"; - shareSpan.style.cursor = "pointer"; - shareSpan.style.marginTop = "8px"; - shareSpan.style.opacity = "0.75"; - shareSpan.style.transition = "opacity .2s"; - shareSpan.title = "分享"; + // msg.appendChild(shareSpan); - shareSpan.onmouseenter = () => shareSpan.style.opacity = "1"; - shareSpan.onmouseleave = () => shareSpan.style.opacity = "0.75"; + // ====================== + // ✅ 复制按钮(新增) + // ====================== + const copySpan = document.createElement("span"); + copySpan.style.display = "inline-flex"; + copySpan.style.alignItems = "center"; + copySpan.style.cursor = "pointer"; + copySpan.style.marginTop = "8px"; + copySpan.style.marginLeft = "10px"; + copySpan.style.opacity = "0.75"; + copySpan.style.transition = "opacity .2s"; + copySpan.title = "复制内容"; - shareSpan.innerHTML = ` - - - + copySpan.onmouseenter = () => copySpan.style.opacity = "1"; + copySpan.onmouseleave = () => copySpan.style.opacity = "0.75"; + + copySpan.innerHTML = ` + `; - shareSpan.onclick = () => { - generatePoster(content, imgUrl); - }; + copySpan.onclick = async () => { + try { + await navigator.clipboard.writeText(content); + copySpan.style.opacity = "1"; + showToast("复制成功") + copySpan.title = "复制成功"; + setTimeout(() => { copySpan.title = "复制内容"; }, 1200); + } catch (err) { + alert("复制失败,可能不支持 HTTPS 环境以外复制。"); + } + }; - msg.appendChild(shareSpan); - - // ====================== - // ✅ 复制按钮(新增) - // ====================== - const copySpan = document.createElement("span"); - copySpan.style.display = "inline-flex"; - copySpan.style.alignItems = "center"; - copySpan.style.cursor = "pointer"; - copySpan.style.marginTop = "8px"; - copySpan.style.marginLeft = "10px"; - copySpan.style.opacity = "0.75"; - copySpan.style.transition = "opacity .2s"; - copySpan.title = "复制内容"; - - copySpan.onmouseenter = () => copySpan.style.opacity = "1"; - copySpan.onmouseleave = () => copySpan.style.opacity = "0.75"; - - copySpan.innerHTML = ` - - - - `; - - copySpan.onclick = async () => { - try { - await navigator.clipboard.writeText(content); - copySpan.style.opacity = "1"; - showToast("复制成功") - copySpan.title = "复制成功"; - setTimeout(() => { copySpan.title = "复制内容"; }, 1200); - } catch (err) { - alert("复制失败,可能不支持 HTTPS 环境以外复制。"); - } - }; - - } + // } setTimeout(() => { msgDiv.appendChild(shareSpan); @@ -3202,6 +3218,20 @@ showToast("提交成功", 'success') } }); + + window.addEventListener('beforeunload', function (event) { + const data = { + userId: userId + } + const res = fetch('https://serviceai.satabot.com/updateAiChatNum', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + + console.log(res, 98888); + debugger; + }) diff --git a/middle.html b/middle.html index 5490940..5addfab 100644 --- a/middle.html +++ b/middle.html @@ -4,12 +4,11 @@ - 新能源智能体 - - + 飒沓光伏智能体 + + - @@ -702,7 +701,9 @@ retryFetch(`https://servicestream.satabot.com/api/wechat/access_token?code=${code}`) .then(res => { userInfo = res; + console.log(res); localStorage.setItem("userInfo", JSON.stringify(res)); + // test(res.openid) getUserByOpenId(res.openid) }).catch(() => { if (!document.querySelector('.weixin-loading').classList.contains("none")) { @@ -713,6 +714,57 @@ }) } + function test(id) { + console.log(id); + const data = { + sourceType: 5, + openId: id, + phonenumber: '', + smsCode: '', + loginPlatForm: 2 + + } + retryFetch("https://serviceri.satabot.com/login", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(data) + }) + .then(res => { + if (res.code == 300) { + // showpop(); // 弹出验证码 + // const _token = "BEAR DHWJDBJDKWJDBDNJEWHBD" + + // setCookie('token', _token); + // const encodedUserId = btoa(unescape(encodeURIComponent("138"))); + // localStorage.setItem('userId', encodedUserId); + // const t = Date.now(); + // document.body.classList.add('fadeout'); + + // window.location.replace(`https://ai.satabot.com?userId=${encodedUserId}&headingimg=${userInfo.headimgurl}&nickname=${userInfo.nickname}&localtoken=${_token}&t=${t}`) + } else if (res.code == 200) { + setCookie('token', res.token); + const encodedUserId = btoa(unescape(encodeURIComponent(res.userId))); + localStorage.setItem('userId', encodedUserId); + const t = Date.now(); + document.body.classList.add('fadeout'); + + window.location.replace(`https://ai.satabot.com?userId=${encodedUserId}&headingimg=${userInfo.headimgurl}&nickname=${userInfo.nickname}&localtoken=${res.token}&t=${t}`) + } + }) + .catch(err => { + console.error("登录请求失败(重试10次后):", err); + showToast("网络异常,请稍后再试"); + }) + .finally(() => { + + console.log("登录请求结束"); + // if (!document.querySelector('.weixin-loading').classList.contains("none")) { + // document.querySelector('.weixin-loading').classList.add("none"); + // } + }); + } function getUserByOpenId(id) { const data = { @@ -733,15 +785,15 @@ .then(res => { if (res.code == 300) { // showpop(); // 弹出验证码 - const _token = "BEAR DHWJDBJDKWJDBDNJEWHBD" + // const _token = "BEAR DHWJDBJDKWJDBDNJEWHBD" - setCookie('token', _token); - const encodedUserId = btoa(unescape(encodeURIComponent("138"))); - localStorage.setItem('userId', encodedUserId); - const t = Date.now(); - document.body.classList.add('fadeout'); + // setCookie('token', _token); + // const encodedUserId = btoa(unescape(encodeURIComponent("138"))); + // localStorage.setItem('userId', encodedUserId); + // const t = Date.now(); + // document.body.classList.add('fadeout'); - window.location.replace(`https://ai.satabot.com?userId=${encodedUserId}&headingimg=${userInfo.headimgurl}&nickname=${userInfo.nickname}&localtoken=${_token}&t=${t}`) + // window.location.replace(`https://ai.satabot.com?userId=${encodedUserId}&headingimg=${userInfo.headimgurl}&nickname=${userInfo.nickname}&localtoken=${_token}&t=${t}`) } else if (res.code == 200) { setCookie('token', res.token); const encodedUserId = btoa(unescape(encodeURIComponent(res.userId))); diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 0b254ca..f412f0a 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -233,6 +233,15 @@ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", diff --git a/node_modules/async-mutex/LICENSE b/node_modules/async-mutex/LICENSE new file mode 100644 index 0000000..cb6f7bf --- /dev/null +++ b/node_modules/async-mutex/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Christian Speckner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/async-mutex/README.md b/node_modules/async-mutex/README.md new file mode 100644 index 0000000..95c30b8 --- /dev/null +++ b/node_modules/async-mutex/README.md @@ -0,0 +1,540 @@ +[![Build status](https://github.com/DirtyHairy/async-mutex/workflows/Build%20and%20Tests/badge.svg)](https://github.com/DirtyHairy/async-mutex/actions?query=workflow%3A%22Build+and+Tests%22) +[![NPM version](https://badge.fury.io/js/async-mutex.svg)](https://badge.fury.io/js/async-mutex) +[![Coverage Status](https://coveralls.io/repos/github/DirtyHairy/async-mutex/badge.svg?branch=master)](https://coveralls.io/github/DirtyHairy/async-mutex?branch=master) + +# What is it? + +This package implements primitives for synchronizing asynchronous operations in +Javascript. + +## Mutex + +The term "mutex" usually refers to a data structure used to synchronize +concurrent processes running on different threads. For example, before accessing +a non-threadsafe resource, a thread will lock the mutex. This is guaranteed +to block the thread until no other thread holds a lock on the mutex and thus +enforces exclusive access to the resource. Once the operation is complete, the +thread releases the lock, allowing other threads to acquire a lock and access the +resource. + +While Javascript is strictly single-threaded, the asynchronous nature of its +execution model allows for race conditions that require similar synchronization +primitives. Consider for example a library communicating with a web worker that +needs to exchange several subsequent messages with the worker in order to achieve +a task. As these messages are exchanged in an asynchronous manner, it is perfectly +possible that the library is called again during this process. Depending on the +way state is handled during the async process, this will lead to race conditions +that are hard to fix and even harder to track down. + +This library solves the problem by applying the concept of mutexes to Javascript. +Locking the mutex will return a promise that resolves once the mutex becomes +available. Once the async process is complete (usually taking multiple +spins of the event loop), a callback supplied to the caller should be called in order +to release the mutex, allowing the next scheduled worker to execute. + +# Semaphore + +Imagine a situation where you need to control access to several instances of +a shared resource. For example, you might want to distribute images between several +worker processes that perform transformations, or you might want to create a web +crawler that performs a defined number of requests in parallel. + +A semaphore is a data structure that is initialized with an arbitrary integer value and that +can be locked multiple times. +As long as the semaphore value is positive, locking it will return the current value +and the locking process will continue execution immediately; the semaphore will +be decremented upon locking. Releasing the lock will increment the semaphore again. + +Once the semaphore has reached zero, the next process that attempts to acquire a lock +will be suspended until another process releases its lock and this increments the semaphore +again. + +This library provides a semaphore implementation for Javascript that is similar to the +mutex implementation described above. + +# How to use it? + +## Installation + +You can install the library into your project via npm + + npm install async-mutex + +The library is written in TypeScript and will work in any environment that +supports ES5, ES6 promises and `Array.isArray`. On ancient browsers, +a shim can be used (e.g. [core-js](https://github.com/zloirock/core-js)). +No external typings are required for using this library with +TypeScript (version >= 2). + +Starting with Node 12.16 and 13.7, native ES6 style imports are supported. + +**WARNING:** Node 13 versions < 13.2.0 fail to import this package correctly. +Node 12 and earlier are fine, as are newer versions of Node 13. + +## Importing + +**CommonJS:** +```javascript +var Mutex = require('async-mutex').Mutex; +var Semaphore = require('async-mutex').Semaphore; +var withTimeout = require('async-mutex').withTimeout; +``` + +**ES6:** +```javascript +import {Mutex, Semaphore, withTimeout} from 'async-mutex'; +``` + +**TypeScript:** +```typescript +import {Mutex, MutexInterface, Semaphore, SemaphoreInterface, withTimeout} from 'async-mutex'; +``` + +With the latest version of Node, native ES6 style imports are supported. + +## Mutex API + +### Creating + +```typescript +const mutex = new Mutex(); +``` + +Create a new mutex. + +### Synchronized code execution + +Promise style: +```typescript +mutex + .runExclusive(() => { + // ... + }) + .then((result) => { + // ... + }); +``` + +async/await: +```typescript +await mutex.runExclusive(async () => { + // ... +}); +``` + +`runExclusive` schedules the supplied callback to be run once the mutex is unlocked. +The function may return a promise. Once the promise is resolved or rejected (or immediately after +execution if an immediate value was returned), +the mutex is released. `runExclusive` returns a promise that adopts the state of the function result. + +The mutex is released and the result rejected if an exception occurs during execution +of the callback. + +### Manual locking / releasing + +Promise style: +```typescript +mutex + .acquire() + .then(function(release) { + // ... + + release(); + }); +``` + +async/await: +```typescript +const release = await mutex.acquire(); +try { + // ... +} finally { + release(); +} +``` + +`acquire` returns an (ES6) promise that will resolve as soon as the mutex is +available. The promise resolves with a function `release` that +must be called once the mutex should be released again. The `release` callback +is idempotent. + +**IMPORTANT:** Failure to call `release` will hold the mutex locked and will +likely deadlock the application. Make sure to call `release` under all circumstances +and handle exceptions accordingly. + +### Unscoped release + +As an alternative to calling the `release` callback returned by `acquire`, the mutex +can be released by calling `release` directly on it: + +```typescript +mutex.release(); +``` + +### Checking whether the mutex is locked + +```typescript +mutex.isLocked(); +``` + +### Cancelling pending locks + +Pending locks can be cancelled by calling `cancel()` on the mutex. This will reject +all pending locks with `E_CANCELED`: + +Promise style: +```typescript +import {E_CANCELED} from 'async-mutex'; + +mutex + .runExclusive(() => { + // ... + }) + .then(() => { + // ... + }) + .catch(e => { + if (e === E_CANCELED) { + // ... + } + }); +``` + +async/await: +```typescript +import {E_CANCELED} from 'async-mutex'; + +try { + await mutex.runExclusive(() => { + // ... + }); +} catch (e) { + if (e === E_CANCELED) { + // ... + } +} +``` + +This works with `acquire`, too: +if `acquire` is used for locking, the resulting promise will reject with `E_CANCELED`. + +The error that is thrown can be customized by passing a different error to the `Mutex` +constructor: + +```typescript +const mutex = new Mutex(new Error('fancy custom error')); +``` + +Note that while all pending locks are cancelled, a currently held lock will not be +revoked. In consequence, the mutex may not be available even after `cancel()` has been called. + +### Waiting until the mutex is available + +You can wait until the mutex is available without locking it by calling `waitForUnlock()`. +This will return a promise that resolve once the mutex can be acquired again. This operation +will not lock the mutex, and there is no guarantee that the mutex will still be available +once an async barrier has been encountered. + +Promise style: +```typescript +mutex + .waitForUnlock() + .then(() => { + // ... + }); +``` + +Async/await: +```typescript +await mutex.waitForUnlock(); +// ... +``` + + +## Semaphore API + +### Creating + +```typescript +const semaphore = new Semaphore(initialValue); +``` + +Creates a new semaphore. `initialValue` is an arbitrary integer that defines the +initial value of the semaphore. + +### Synchronized code execution + +Promise style: +```typescript +semaphore + .runExclusive(function(value) { + // ... + }) + .then(function(result) { + // ... + }); +``` + +async/await: +```typescript +await semaphore.runExclusive(async (value) => { + // ... +}); +``` + +`runExclusive` schedules the supplied callback to be run once the semaphore is available. +The callback will receive the current value of the semaphore as its argument. +The function may return a promise. Once the promise is resolved or rejected (or immediately after +execution if an immediate value was returned), +the semaphore is released. `runExclusive` returns a promise that adopts the state of the function result. + +The semaphore is released and the result rejected if an exception occurs during execution +of the callback. + +`runExclusive` accepts a first optional argument `weight`. Specifying a `weight` will decrement the +semaphore by the specified value, and the callback will only be invoked once the semaphore's +value greater or equal to `weight`. + +`runExclusive` accepts a second optional argument `priority`. Specifying a greater value for `priority` +tells the scheduler to run this task before other tasks. `priority` can be any real number. The default +is zero. + +### Manual locking / releasing + +Promise style: +```typescript +semaphore + .acquire() + .then(function([value, release]) { + // ... + + release(); + }); +``` + +async/await: +```typescript +const [value, release] = await semaphore.acquire(); +try { + // ... +} finally { + release(); +} +``` + +`acquire` returns an (ES6) promise that will resolve as soon as the semaphore is +available. The promise resolves to an array with the +first entry being the current value of the semaphore, and the second value a +function that must be called to release the semaphore once the critical operation +has completed. The `release` callback is idempotent. + +**IMPORTANT:** Failure to call `release` will hold the semaphore locked and will +likely deadlock the application. Make sure to call `release` under all circumstances +and handle exceptions accordingly. + +`acquire` accepts a first optional argument `weight`. Specifying a `weight` will decrement the +semaphore by the specified value, and the semaphore will only be acquired once its +value is greater or equal to `weight`. + +`acquire` accepts a second optional argument `priority`. Specifying a greater value for `priority` +tells the scheduler to release the semaphore to the caller before other callers. `priority` can be +any real number. The default is zero. + +### Unscoped release + +As an alternative to calling the `release` callback returned by `acquire`, the semaphore +can be released by calling `release` directly on it: + +```typescript +semaphore.release(); +``` + +`release` accepts an optional argument `weight` and increments the semaphore accordingly. + +**IMPORTANT:** Releasing a previously acquired semaphore with the releaser that was +returned by acquire will automatically increment the semaphore by the correct weight. If +you release by calling the unscoped `release` you have to supply the correct weight +yourself! + +### Getting the semaphore value + +```typescript +semaphore.getValue() +``` + +### Checking whether the semaphore is locked + +```typescript +semaphore.isLocked(); +``` + +The semaphore is considered to be locked if its value is either zero or negative. + +### Setting the semaphore value + +The value of a semaphore can be set directly to a desired value. A positive value will +cause the semaphore to schedule any pending waiters accordingly. + +```typescript +semaphore.setValue(); +``` + +### Cancelling pending locks + +Pending locks can be cancelled by calling `cancel()` on the semaphore. This will reject +all pending locks with `E_CANCELED`: + +Promise style: +```typescript +import {E_CANCELED} from 'async-mutex'; + +semaphore + .runExclusive(() => { + // ... + }) + .then(() => { + // ... + }) + .catch(e => { + if (e === E_CANCELED) { + // ... + } + }); +``` + +async/await: +```typescript +import {E_CANCELED} from 'async-mutex'; + +try { + await semaphore.runExclusive(() => { + // ... + }); +} catch (e) { + if (e === E_CANCELED) { + // ... + } +} +``` + +This works with `acquire`, too: +if `acquire` is used for locking, the resulting promise will reject with `E_CANCELED`. + +The error that is thrown can be customized by passing a different error to the `Semaphore` +constructor: + +```typescript +const semaphore = new Semaphore(2, new Error('fancy custom error')); +``` + +Note that while all pending locks are cancelled, any currently held locks will not be +revoked. In consequence, the semaphore may not be available even after `cancel()` has been called. + +### Waiting until the semaphore is available + +You can wait until the semaphore is available without locking it by calling `waitForUnlock()`. +This will return a promise that resolve once the semaphore can be acquired again. This operation +will not lock the semaphore, and there is no guarantee that the semaphore will still be available +once an async barrier has been encountered. + +Promise style: +```typescript +semaphore + .waitForUnlock() + .then(() => { + // ... + }); +``` + +Async/await: +```typescript +await semaphore.waitForUnlock(); +// ... +``` + +`waitForUnlock` accepts optional arguments `weight` and `priority`. The promise will resolve as soon +as it is possible to `acquire` the semaphore with the given weight and priority. Scheduled tasks with +the greatest `priority` values execute first. + + +## Limiting the time waiting for a mutex or semaphore to become available + +Sometimes it is desirable to limit the time a program waits for a mutex or +semaphore to become available. The `withTimeout` decorator can be applied +to both semaphores and mutexes and changes the behavior of `acquire` and +`runExclusive` accordingly. + +```typescript +import {withTimeout, E_TIMEOUT} from 'async-mutex'; + +const mutexWithTimeout = withTimeout(new Mutex(), 100); +const semaphoreWithTimeout = withTimeout(new Semaphore(5), 100); +``` + +The API of the decorated mutex or semaphore is unchanged. + +The second argument of `withTimeout` is the timeout in milliseconds. After the +timeout is exceeded, the promise returned by `acquire` and `runExclusive` will +reject with `E_TIMEOUT`. The latter will not run the provided callback in case +of an timeout. + +The third argument of `withTimeout` is optional and can be used to +customize the error with which the promise is rejected. + +```typescript +const mutexWithTimeout = withTimeout(new Mutex(), 100, new Error('new fancy error')); +const semaphoreWithTimeout = withTimeout(new Semaphore(5), 100, new Error('new fancy error')); +``` + +### Failing early if the mutex or semaphore is not available + +A shortcut exists for the case where you do not want to wait for a lock to +be available at all. The `tryAcquire` decorator can be applied to both mutexes +and semaphores and changes the behavior of `acquire` and `runExclusive` to +immediately throw `E_ALREADY_LOCKED` if the mutex is not available. + +Promise style: +```typescript +import {tryAcquire, E_ALREADY_LOCKED} from 'async-mutex'; + +tryAcquire(semaphoreOrMutex) + .runExclusive(() => { + // ... + }) + .then(() => { + // ... + }) + .catch(e => { + if (e === E_ALREADY_LOCKED) { + // ... + } + }); +``` + +async/await: +```typescript +import {tryAcquire, E_ALREADY_LOCKED} from 'async-mutex'; + +try { + await tryAcquire(semaphoreOrMutex).runExclusive(() => { + // ... + }); +} catch (e) { + if (e === E_ALREADY_LOCKED) { + // ... + } +} +``` + +Again, the error can be customized by providing a custom error as second argument to +`tryAcquire`. + +```typescript +tryAcquire(semaphoreOrMutex, new Error('new fancy error')) + .runExclusive(() => { + // ... + }); +``` +# License + +Feel free to use this library under the conditions of the MIT license. diff --git a/node_modules/async-mutex/es6/Mutex.js b/node_modules/async-mutex/es6/Mutex.js new file mode 100644 index 0000000..0a06ff9 --- /dev/null +++ b/node_modules/async-mutex/es6/Mutex.js @@ -0,0 +1,41 @@ +import { __awaiter, __generator } from "tslib"; +import Semaphore from './Semaphore'; +var Mutex = /** @class */ (function () { + function Mutex(cancelError) { + this._semaphore = new Semaphore(1, cancelError); + } + Mutex.prototype.acquire = function () { + return __awaiter(this, arguments, void 0, function (priority) { + var _a, releaser; + if (priority === void 0) { priority = 0; } + return __generator(this, function (_b) { + switch (_b.label) { + case 0: return [4 /*yield*/, this._semaphore.acquire(1, priority)]; + case 1: + _a = _b.sent(), releaser = _a[1]; + return [2 /*return*/, releaser]; + } + }); + }); + }; + Mutex.prototype.runExclusive = function (callback, priority) { + if (priority === void 0) { priority = 0; } + return this._semaphore.runExclusive(function () { return callback(); }, 1, priority); + }; + Mutex.prototype.isLocked = function () { + return this._semaphore.isLocked(); + }; + Mutex.prototype.waitForUnlock = function (priority) { + if (priority === void 0) { priority = 0; } + return this._semaphore.waitForUnlock(1, priority); + }; + Mutex.prototype.release = function () { + if (this._semaphore.isLocked()) + this._semaphore.release(); + }; + Mutex.prototype.cancel = function () { + return this._semaphore.cancel(); + }; + return Mutex; +}()); +export default Mutex; diff --git a/node_modules/async-mutex/es6/MutexInterface.js b/node_modules/async-mutex/es6/MutexInterface.js new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/node_modules/async-mutex/es6/MutexInterface.js @@ -0,0 +1 @@ +export {}; diff --git a/node_modules/async-mutex/es6/Semaphore.js b/node_modules/async-mutex/es6/Semaphore.js new file mode 100644 index 0000000..5eb505b --- /dev/null +++ b/node_modules/async-mutex/es6/Semaphore.js @@ -0,0 +1,153 @@ +import { __awaiter, __generator } from "tslib"; +import { E_CANCELED } from './errors'; +var Semaphore = /** @class */ (function () { + function Semaphore(_value, _cancelError) { + if (_cancelError === void 0) { _cancelError = E_CANCELED; } + this._value = _value; + this._cancelError = _cancelError; + this._queue = []; + this._weightedWaiters = []; + } + Semaphore.prototype.acquire = function (weight, priority) { + var _this = this; + if (weight === void 0) { weight = 1; } + if (priority === void 0) { priority = 0; } + if (weight <= 0) + throw new Error("invalid weight ".concat(weight, ": must be positive")); + return new Promise(function (resolve, reject) { + var task = { resolve: resolve, reject: reject, weight: weight, priority: priority }; + var i = findIndexFromEnd(_this._queue, function (other) { return priority <= other.priority; }); + if (i === -1 && weight <= _this._value) { + // Needs immediate dispatch, skip the queue + _this._dispatchItem(task); + } + else { + _this._queue.splice(i + 1, 0, task); + } + }); + }; + Semaphore.prototype.runExclusive = function (callback_1) { + return __awaiter(this, arguments, void 0, function (callback, weight, priority) { + var _a, value, release; + if (weight === void 0) { weight = 1; } + if (priority === void 0) { priority = 0; } + return __generator(this, function (_b) { + switch (_b.label) { + case 0: return [4 /*yield*/, this.acquire(weight, priority)]; + case 1: + _a = _b.sent(), value = _a[0], release = _a[1]; + _b.label = 2; + case 2: + _b.trys.push([2, , 4, 5]); + return [4 /*yield*/, callback(value)]; + case 3: return [2 /*return*/, _b.sent()]; + case 4: + release(); + return [7 /*endfinally*/]; + case 5: return [2 /*return*/]; + } + }); + }); + }; + Semaphore.prototype.waitForUnlock = function (weight, priority) { + var _this = this; + if (weight === void 0) { weight = 1; } + if (priority === void 0) { priority = 0; } + if (weight <= 0) + throw new Error("invalid weight ".concat(weight, ": must be positive")); + if (this._couldLockImmediately(weight, priority)) { + return Promise.resolve(); + } + else { + return new Promise(function (resolve) { + if (!_this._weightedWaiters[weight - 1]) + _this._weightedWaiters[weight - 1] = []; + insertSorted(_this._weightedWaiters[weight - 1], { resolve: resolve, priority: priority }); + }); + } + }; + Semaphore.prototype.isLocked = function () { + return this._value <= 0; + }; + Semaphore.prototype.getValue = function () { + return this._value; + }; + Semaphore.prototype.setValue = function (value) { + this._value = value; + this._dispatchQueue(); + }; + Semaphore.prototype.release = function (weight) { + if (weight === void 0) { weight = 1; } + if (weight <= 0) + throw new Error("invalid weight ".concat(weight, ": must be positive")); + this._value += weight; + this._dispatchQueue(); + }; + Semaphore.prototype.cancel = function () { + var _this = this; + this._queue.forEach(function (entry) { return entry.reject(_this._cancelError); }); + this._queue = []; + }; + Semaphore.prototype._dispatchQueue = function () { + this._drainUnlockWaiters(); + while (this._queue.length > 0 && this._queue[0].weight <= this._value) { + this._dispatchItem(this._queue.shift()); + this._drainUnlockWaiters(); + } + }; + Semaphore.prototype._dispatchItem = function (item) { + var previousValue = this._value; + this._value -= item.weight; + item.resolve([previousValue, this._newReleaser(item.weight)]); + }; + Semaphore.prototype._newReleaser = function (weight) { + var _this = this; + var called = false; + return function () { + if (called) + return; + called = true; + _this.release(weight); + }; + }; + Semaphore.prototype._drainUnlockWaiters = function () { + if (this._queue.length === 0) { + for (var weight = this._value; weight > 0; weight--) { + var waiters = this._weightedWaiters[weight - 1]; + if (!waiters) + continue; + waiters.forEach(function (waiter) { return waiter.resolve(); }); + this._weightedWaiters[weight - 1] = []; + } + } + else { + var queuedPriority_1 = this._queue[0].priority; + for (var weight = this._value; weight > 0; weight--) { + var waiters = this._weightedWaiters[weight - 1]; + if (!waiters) + continue; + var i = waiters.findIndex(function (waiter) { return waiter.priority <= queuedPriority_1; }); + (i === -1 ? waiters : waiters.splice(0, i)) + .forEach((function (waiter) { return waiter.resolve(); })); + } + } + }; + Semaphore.prototype._couldLockImmediately = function (weight, priority) { + return (this._queue.length === 0 || this._queue[0].priority < priority) && + weight <= this._value; + }; + return Semaphore; +}()); +function insertSorted(a, v) { + var i = findIndexFromEnd(a, function (other) { return v.priority <= other.priority; }); + a.splice(i + 1, 0, v); +} +function findIndexFromEnd(a, predicate) { + for (var i = a.length - 1; i >= 0; i--) { + if (predicate(a[i])) { + return i; + } + } + return -1; +} +export default Semaphore; diff --git a/node_modules/async-mutex/es6/SemaphoreInterface.js b/node_modules/async-mutex/es6/SemaphoreInterface.js new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/node_modules/async-mutex/es6/SemaphoreInterface.js @@ -0,0 +1 @@ +export {}; diff --git a/node_modules/async-mutex/es6/errors.js b/node_modules/async-mutex/es6/errors.js new file mode 100644 index 0000000..6b3eddd --- /dev/null +++ b/node_modules/async-mutex/es6/errors.js @@ -0,0 +1,3 @@ +export var E_TIMEOUT = new Error('timeout while waiting for mutex to become available'); +export var E_ALREADY_LOCKED = new Error('mutex already locked'); +export var E_CANCELED = new Error('request for lock canceled'); diff --git a/node_modules/async-mutex/es6/index.js b/node_modules/async-mutex/es6/index.js new file mode 100644 index 0000000..f1859de --- /dev/null +++ b/node_modules/async-mutex/es6/index.js @@ -0,0 +1,5 @@ +export { default as Mutex } from './Mutex'; +export { default as Semaphore } from './Semaphore'; +export { withTimeout } from './withTimeout'; +export { tryAcquire } from './tryAcquire'; +export * from './errors'; diff --git a/node_modules/async-mutex/es6/tryAcquire.js b/node_modules/async-mutex/es6/tryAcquire.js new file mode 100644 index 0000000..20e5781 --- /dev/null +++ b/node_modules/async-mutex/es6/tryAcquire.js @@ -0,0 +1,8 @@ +import { E_ALREADY_LOCKED } from './errors'; +import { withTimeout } from './withTimeout'; +// eslint-disable-next-lisne @typescript-eslint/explicit-module-boundary-types +export function tryAcquire(sync, alreadyAcquiredError) { + if (alreadyAcquiredError === void 0) { alreadyAcquiredError = E_ALREADY_LOCKED; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return withTimeout(sync, 0, alreadyAcquiredError); +} diff --git a/node_modules/async-mutex/es6/withTimeout.js b/node_modules/async-mutex/es6/withTimeout.js new file mode 100644 index 0000000..fb5267c --- /dev/null +++ b/node_modules/async-mutex/es6/withTimeout.js @@ -0,0 +1,124 @@ +import { __awaiter, __generator } from "tslib"; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { E_TIMEOUT } from './errors'; +export function withTimeout(sync, timeout, timeoutError) { + var _this = this; + if (timeoutError === void 0) { timeoutError = E_TIMEOUT; } + return { + acquire: function (weightOrPriority, priority) { + var weight; + if (isSemaphore(sync)) { + weight = weightOrPriority; + } + else { + weight = undefined; + priority = weightOrPriority; + } + if (weight !== undefined && weight <= 0) { + throw new Error("invalid weight ".concat(weight, ": must be positive")); + } + return new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () { + var isTimeout, handle, ticket, release, e_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + isTimeout = false; + handle = setTimeout(function () { + isTimeout = true; + reject(timeoutError); + }, timeout); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + return [4 /*yield*/, (isSemaphore(sync) + ? sync.acquire(weight, priority) + : sync.acquire(priority))]; + case 2: + ticket = _a.sent(); + if (isTimeout) { + release = Array.isArray(ticket) ? ticket[1] : ticket; + release(); + } + else { + clearTimeout(handle); + resolve(ticket); + } + return [3 /*break*/, 4]; + case 3: + e_1 = _a.sent(); + if (!isTimeout) { + clearTimeout(handle); + reject(e_1); + } + return [3 /*break*/, 4]; + case 4: return [2 /*return*/]; + } + }); + }); }); + }, + runExclusive: function (callback, weight, priority) { + return __awaiter(this, void 0, void 0, function () { + var release, ticket; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + release = function () { return undefined; }; + _a.label = 1; + case 1: + _a.trys.push([1, , 7, 8]); + return [4 /*yield*/, this.acquire(weight, priority)]; + case 2: + ticket = _a.sent(); + if (!Array.isArray(ticket)) return [3 /*break*/, 4]; + release = ticket[1]; + return [4 /*yield*/, callback(ticket[0])]; + case 3: return [2 /*return*/, _a.sent()]; + case 4: + release = ticket; + return [4 /*yield*/, callback()]; + case 5: return [2 /*return*/, _a.sent()]; + case 6: return [3 /*break*/, 8]; + case 7: + release(); + return [7 /*endfinally*/]; + case 8: return [2 /*return*/]; + } + }); + }); + }, + release: function (weight) { + sync.release(weight); + }, + cancel: function () { + return sync.cancel(); + }, + waitForUnlock: function (weightOrPriority, priority) { + var weight; + if (isSemaphore(sync)) { + weight = weightOrPriority; + } + else { + weight = undefined; + priority = weightOrPriority; + } + if (weight !== undefined && weight <= 0) { + throw new Error("invalid weight ".concat(weight, ": must be positive")); + } + return new Promise(function (resolve, reject) { + var handle = setTimeout(function () { return reject(timeoutError); }, timeout); + (isSemaphore(sync) + ? sync.waitForUnlock(weight, priority) + : sync.waitForUnlock(priority)).then(function () { + clearTimeout(handle); + resolve(); + }); + }); + }, + isLocked: function () { return sync.isLocked(); }, + getValue: function () { return sync.getValue(); }, + setValue: function (value) { return sync.setValue(value); }, + }; +} +function isSemaphore(sync) { + return sync.getValue !== undefined; +} diff --git a/node_modules/async-mutex/index.mjs b/node_modules/async-mutex/index.mjs new file mode 100644 index 0000000..d32d6a5 --- /dev/null +++ b/node_modules/async-mutex/index.mjs @@ -0,0 +1,291 @@ +const E_TIMEOUT = new Error('timeout while waiting for mutex to become available'); +const E_ALREADY_LOCKED = new Error('mutex already locked'); +const E_CANCELED = new Error('request for lock canceled'); + +var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +class Semaphore { + constructor(_value, _cancelError = E_CANCELED) { + this._value = _value; + this._cancelError = _cancelError; + this._queue = []; + this._weightedWaiters = []; + } + acquire(weight = 1, priority = 0) { + if (weight <= 0) + throw new Error(`invalid weight ${weight}: must be positive`); + return new Promise((resolve, reject) => { + const task = { resolve, reject, weight, priority }; + const i = findIndexFromEnd(this._queue, (other) => priority <= other.priority); + if (i === -1 && weight <= this._value) { + // Needs immediate dispatch, skip the queue + this._dispatchItem(task); + } + else { + this._queue.splice(i + 1, 0, task); + } + }); + } + runExclusive(callback_1) { + return __awaiter$2(this, arguments, void 0, function* (callback, weight = 1, priority = 0) { + const [value, release] = yield this.acquire(weight, priority); + try { + return yield callback(value); + } + finally { + release(); + } + }); + } + waitForUnlock(weight = 1, priority = 0) { + if (weight <= 0) + throw new Error(`invalid weight ${weight}: must be positive`); + if (this._couldLockImmediately(weight, priority)) { + return Promise.resolve(); + } + else { + return new Promise((resolve) => { + if (!this._weightedWaiters[weight - 1]) + this._weightedWaiters[weight - 1] = []; + insertSorted(this._weightedWaiters[weight - 1], { resolve, priority }); + }); + } + } + isLocked() { + return this._value <= 0; + } + getValue() { + return this._value; + } + setValue(value) { + this._value = value; + this._dispatchQueue(); + } + release(weight = 1) { + if (weight <= 0) + throw new Error(`invalid weight ${weight}: must be positive`); + this._value += weight; + this._dispatchQueue(); + } + cancel() { + this._queue.forEach((entry) => entry.reject(this._cancelError)); + this._queue = []; + } + _dispatchQueue() { + this._drainUnlockWaiters(); + while (this._queue.length > 0 && this._queue[0].weight <= this._value) { + this._dispatchItem(this._queue.shift()); + this._drainUnlockWaiters(); + } + } + _dispatchItem(item) { + const previousValue = this._value; + this._value -= item.weight; + item.resolve([previousValue, this._newReleaser(item.weight)]); + } + _newReleaser(weight) { + let called = false; + return () => { + if (called) + return; + called = true; + this.release(weight); + }; + } + _drainUnlockWaiters() { + if (this._queue.length === 0) { + for (let weight = this._value; weight > 0; weight--) { + const waiters = this._weightedWaiters[weight - 1]; + if (!waiters) + continue; + waiters.forEach((waiter) => waiter.resolve()); + this._weightedWaiters[weight - 1] = []; + } + } + else { + const queuedPriority = this._queue[0].priority; + for (let weight = this._value; weight > 0; weight--) { + const waiters = this._weightedWaiters[weight - 1]; + if (!waiters) + continue; + const i = waiters.findIndex((waiter) => waiter.priority <= queuedPriority); + (i === -1 ? waiters : waiters.splice(0, i)) + .forEach((waiter => waiter.resolve())); + } + } + } + _couldLockImmediately(weight, priority) { + return (this._queue.length === 0 || this._queue[0].priority < priority) && + weight <= this._value; + } +} +function insertSorted(a, v) { + const i = findIndexFromEnd(a, (other) => v.priority <= other.priority); + a.splice(i + 1, 0, v); +} +function findIndexFromEnd(a, predicate) { + for (let i = a.length - 1; i >= 0; i--) { + if (predicate(a[i])) { + return i; + } + } + return -1; +} + +var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +class Mutex { + constructor(cancelError) { + this._semaphore = new Semaphore(1, cancelError); + } + acquire() { + return __awaiter$1(this, arguments, void 0, function* (priority = 0) { + const [, releaser] = yield this._semaphore.acquire(1, priority); + return releaser; + }); + } + runExclusive(callback, priority = 0) { + return this._semaphore.runExclusive(() => callback(), 1, priority); + } + isLocked() { + return this._semaphore.isLocked(); + } + waitForUnlock(priority = 0) { + return this._semaphore.waitForUnlock(1, priority); + } + release() { + if (this._semaphore.isLocked()) + this._semaphore.release(); + } + cancel() { + return this._semaphore.cancel(); + } +} + +var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +function withTimeout(sync, timeout, timeoutError = E_TIMEOUT) { + return { + acquire: (weightOrPriority, priority) => { + let weight; + if (isSemaphore(sync)) { + weight = weightOrPriority; + } + else { + weight = undefined; + priority = weightOrPriority; + } + if (weight !== undefined && weight <= 0) { + throw new Error(`invalid weight ${weight}: must be positive`); + } + return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { + let isTimeout = false; + const handle = setTimeout(() => { + isTimeout = true; + reject(timeoutError); + }, timeout); + try { + const ticket = yield (isSemaphore(sync) + ? sync.acquire(weight, priority) + : sync.acquire(priority)); + if (isTimeout) { + const release = Array.isArray(ticket) ? ticket[1] : ticket; + release(); + } + else { + clearTimeout(handle); + resolve(ticket); + } + } + catch (e) { + if (!isTimeout) { + clearTimeout(handle); + reject(e); + } + } + })); + }, + runExclusive(callback, weight, priority) { + return __awaiter(this, void 0, void 0, function* () { + let release = () => undefined; + try { + const ticket = yield this.acquire(weight, priority); + if (Array.isArray(ticket)) { + release = ticket[1]; + return yield callback(ticket[0]); + } + else { + release = ticket; + return yield callback(); + } + } + finally { + release(); + } + }); + }, + release(weight) { + sync.release(weight); + }, + cancel() { + return sync.cancel(); + }, + waitForUnlock: (weightOrPriority, priority) => { + let weight; + if (isSemaphore(sync)) { + weight = weightOrPriority; + } + else { + weight = undefined; + priority = weightOrPriority; + } + if (weight !== undefined && weight <= 0) { + throw new Error(`invalid weight ${weight}: must be positive`); + } + return new Promise((resolve, reject) => { + const handle = setTimeout(() => reject(timeoutError), timeout); + (isSemaphore(sync) + ? sync.waitForUnlock(weight, priority) + : sync.waitForUnlock(priority)).then(() => { + clearTimeout(handle); + resolve(); + }); + }); + }, + isLocked: () => sync.isLocked(), + getValue: () => sync.getValue(), + setValue: (value) => sync.setValue(value), + }; +} +function isSemaphore(sync) { + return sync.getValue !== undefined; +} + +// eslint-disable-next-lisne @typescript-eslint/explicit-module-boundary-types +function tryAcquire(sync, alreadyAcquiredError = E_ALREADY_LOCKED) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return withTimeout(sync, 0, alreadyAcquiredError); +} + +export { E_ALREADY_LOCKED, E_CANCELED, E_TIMEOUT, Mutex, Semaphore, tryAcquire, withTimeout }; diff --git a/node_modules/async-mutex/lib/Mutex.d.ts b/node_modules/async-mutex/lib/Mutex.d.ts new file mode 100644 index 0000000..420f63b --- /dev/null +++ b/node_modules/async-mutex/lib/Mutex.d.ts @@ -0,0 +1,12 @@ +import MutexInterface from './MutexInterface'; +declare class Mutex implements MutexInterface { + constructor(cancelError?: Error); + acquire(priority?: number): Promise; + runExclusive(callback: MutexInterface.Worker, priority?: number): Promise; + isLocked(): boolean; + waitForUnlock(priority?: number): Promise; + release(): void; + cancel(): void; + private _semaphore; +} +export default Mutex; diff --git a/node_modules/async-mutex/lib/Mutex.js b/node_modules/async-mutex/lib/Mutex.js new file mode 100644 index 0000000..bc2097a --- /dev/null +++ b/node_modules/async-mutex/lib/Mutex.js @@ -0,0 +1,43 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var Semaphore_1 = require("./Semaphore"); +var Mutex = /** @class */ (function () { + function Mutex(cancelError) { + this._semaphore = new Semaphore_1.default(1, cancelError); + } + Mutex.prototype.acquire = function () { + return tslib_1.__awaiter(this, arguments, void 0, function (priority) { + var _a, releaser; + if (priority === void 0) { priority = 0; } + return tslib_1.__generator(this, function (_b) { + switch (_b.label) { + case 0: return [4 /*yield*/, this._semaphore.acquire(1, priority)]; + case 1: + _a = _b.sent(), releaser = _a[1]; + return [2 /*return*/, releaser]; + } + }); + }); + }; + Mutex.prototype.runExclusive = function (callback, priority) { + if (priority === void 0) { priority = 0; } + return this._semaphore.runExclusive(function () { return callback(); }, 1, priority); + }; + Mutex.prototype.isLocked = function () { + return this._semaphore.isLocked(); + }; + Mutex.prototype.waitForUnlock = function (priority) { + if (priority === void 0) { priority = 0; } + return this._semaphore.waitForUnlock(1, priority); + }; + Mutex.prototype.release = function () { + if (this._semaphore.isLocked()) + this._semaphore.release(); + }; + Mutex.prototype.cancel = function () { + return this._semaphore.cancel(); + }; + return Mutex; +}()); +exports.default = Mutex; diff --git a/node_modules/async-mutex/lib/MutexInterface.d.ts b/node_modules/async-mutex/lib/MutexInterface.d.ts new file mode 100644 index 0000000..53b37aa --- /dev/null +++ b/node_modules/async-mutex/lib/MutexInterface.d.ts @@ -0,0 +1,17 @@ +interface MutexInterface { + acquire(priority?: number): Promise; + runExclusive(callback: MutexInterface.Worker, priority?: number): Promise; + waitForUnlock(priority?: number): Promise; + isLocked(): boolean; + release(): void; + cancel(): void; +} +declare namespace MutexInterface { + interface Releaser { + (): void; + } + interface Worker { + (): Promise | T; + } +} +export default MutexInterface; diff --git a/node_modules/async-mutex/lib/MutexInterface.js b/node_modules/async-mutex/lib/MutexInterface.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/node_modules/async-mutex/lib/MutexInterface.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/node_modules/async-mutex/lib/Semaphore.d.ts b/node_modules/async-mutex/lib/Semaphore.d.ts new file mode 100644 index 0000000..e29b23d --- /dev/null +++ b/node_modules/async-mutex/lib/Semaphore.d.ts @@ -0,0 +1,22 @@ +import SemaphoreInterface from './SemaphoreInterface'; +declare class Semaphore implements SemaphoreInterface { + private _value; + private _cancelError; + constructor(_value: number, _cancelError?: Error); + acquire(weight?: number, priority?: number): Promise<[number, SemaphoreInterface.Releaser]>; + runExclusive(callback: SemaphoreInterface.Worker, weight?: number, priority?: number): Promise; + waitForUnlock(weight?: number, priority?: number): Promise; + isLocked(): boolean; + getValue(): number; + setValue(value: number): void; + release(weight?: number): void; + cancel(): void; + private _dispatchQueue; + private _dispatchItem; + private _newReleaser; + private _drainUnlockWaiters; + private _couldLockImmediately; + private _queue; + private _weightedWaiters; +} +export default Semaphore; diff --git a/node_modules/async-mutex/lib/Semaphore.js b/node_modules/async-mutex/lib/Semaphore.js new file mode 100644 index 0000000..77c76e6 --- /dev/null +++ b/node_modules/async-mutex/lib/Semaphore.js @@ -0,0 +1,155 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var errors_1 = require("./errors"); +var Semaphore = /** @class */ (function () { + function Semaphore(_value, _cancelError) { + if (_cancelError === void 0) { _cancelError = errors_1.E_CANCELED; } + this._value = _value; + this._cancelError = _cancelError; + this._queue = []; + this._weightedWaiters = []; + } + Semaphore.prototype.acquire = function (weight, priority) { + var _this = this; + if (weight === void 0) { weight = 1; } + if (priority === void 0) { priority = 0; } + if (weight <= 0) + throw new Error("invalid weight ".concat(weight, ": must be positive")); + return new Promise(function (resolve, reject) { + var task = { resolve: resolve, reject: reject, weight: weight, priority: priority }; + var i = findIndexFromEnd(_this._queue, function (other) { return priority <= other.priority; }); + if (i === -1 && weight <= _this._value) { + // Needs immediate dispatch, skip the queue + _this._dispatchItem(task); + } + else { + _this._queue.splice(i + 1, 0, task); + } + }); + }; + Semaphore.prototype.runExclusive = function (callback_1) { + return tslib_1.__awaiter(this, arguments, void 0, function (callback, weight, priority) { + var _a, value, release; + if (weight === void 0) { weight = 1; } + if (priority === void 0) { priority = 0; } + return tslib_1.__generator(this, function (_b) { + switch (_b.label) { + case 0: return [4 /*yield*/, this.acquire(weight, priority)]; + case 1: + _a = _b.sent(), value = _a[0], release = _a[1]; + _b.label = 2; + case 2: + _b.trys.push([2, , 4, 5]); + return [4 /*yield*/, callback(value)]; + case 3: return [2 /*return*/, _b.sent()]; + case 4: + release(); + return [7 /*endfinally*/]; + case 5: return [2 /*return*/]; + } + }); + }); + }; + Semaphore.prototype.waitForUnlock = function (weight, priority) { + var _this = this; + if (weight === void 0) { weight = 1; } + if (priority === void 0) { priority = 0; } + if (weight <= 0) + throw new Error("invalid weight ".concat(weight, ": must be positive")); + if (this._couldLockImmediately(weight, priority)) { + return Promise.resolve(); + } + else { + return new Promise(function (resolve) { + if (!_this._weightedWaiters[weight - 1]) + _this._weightedWaiters[weight - 1] = []; + insertSorted(_this._weightedWaiters[weight - 1], { resolve: resolve, priority: priority }); + }); + } + }; + Semaphore.prototype.isLocked = function () { + return this._value <= 0; + }; + Semaphore.prototype.getValue = function () { + return this._value; + }; + Semaphore.prototype.setValue = function (value) { + this._value = value; + this._dispatchQueue(); + }; + Semaphore.prototype.release = function (weight) { + if (weight === void 0) { weight = 1; } + if (weight <= 0) + throw new Error("invalid weight ".concat(weight, ": must be positive")); + this._value += weight; + this._dispatchQueue(); + }; + Semaphore.prototype.cancel = function () { + var _this = this; + this._queue.forEach(function (entry) { return entry.reject(_this._cancelError); }); + this._queue = []; + }; + Semaphore.prototype._dispatchQueue = function () { + this._drainUnlockWaiters(); + while (this._queue.length > 0 && this._queue[0].weight <= this._value) { + this._dispatchItem(this._queue.shift()); + this._drainUnlockWaiters(); + } + }; + Semaphore.prototype._dispatchItem = function (item) { + var previousValue = this._value; + this._value -= item.weight; + item.resolve([previousValue, this._newReleaser(item.weight)]); + }; + Semaphore.prototype._newReleaser = function (weight) { + var _this = this; + var called = false; + return function () { + if (called) + return; + called = true; + _this.release(weight); + }; + }; + Semaphore.prototype._drainUnlockWaiters = function () { + if (this._queue.length === 0) { + for (var weight = this._value; weight > 0; weight--) { + var waiters = this._weightedWaiters[weight - 1]; + if (!waiters) + continue; + waiters.forEach(function (waiter) { return waiter.resolve(); }); + this._weightedWaiters[weight - 1] = []; + } + } + else { + var queuedPriority_1 = this._queue[0].priority; + for (var weight = this._value; weight > 0; weight--) { + var waiters = this._weightedWaiters[weight - 1]; + if (!waiters) + continue; + var i = waiters.findIndex(function (waiter) { return waiter.priority <= queuedPriority_1; }); + (i === -1 ? waiters : waiters.splice(0, i)) + .forEach((function (waiter) { return waiter.resolve(); })); + } + } + }; + Semaphore.prototype._couldLockImmediately = function (weight, priority) { + return (this._queue.length === 0 || this._queue[0].priority < priority) && + weight <= this._value; + }; + return Semaphore; +}()); +function insertSorted(a, v) { + var i = findIndexFromEnd(a, function (other) { return v.priority <= other.priority; }); + a.splice(i + 1, 0, v); +} +function findIndexFromEnd(a, predicate) { + for (var i = a.length - 1; i >= 0; i--) { + if (predicate(a[i])) { + return i; + } + } + return -1; +} +exports.default = Semaphore; diff --git a/node_modules/async-mutex/lib/SemaphoreInterface.d.ts b/node_modules/async-mutex/lib/SemaphoreInterface.d.ts new file mode 100644 index 0000000..84d6932 --- /dev/null +++ b/node_modules/async-mutex/lib/SemaphoreInterface.d.ts @@ -0,0 +1,19 @@ +interface SemaphoreInterface { + acquire(weight?: number, priority?: number): Promise<[number, SemaphoreInterface.Releaser]>; + runExclusive(callback: SemaphoreInterface.Worker, weight?: number, priority?: number): Promise; + waitForUnlock(weight?: number, priority?: number): Promise; + isLocked(): boolean; + getValue(): number; + setValue(value: number): void; + release(weight?: number): void; + cancel(): void; +} +declare namespace SemaphoreInterface { + interface Releaser { + (): void; + } + interface Worker { + (value: number): Promise | T; + } +} +export default SemaphoreInterface; diff --git a/node_modules/async-mutex/lib/SemaphoreInterface.js b/node_modules/async-mutex/lib/SemaphoreInterface.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/node_modules/async-mutex/lib/SemaphoreInterface.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/node_modules/async-mutex/lib/errors.d.ts b/node_modules/async-mutex/lib/errors.d.ts new file mode 100644 index 0000000..52d2db3 --- /dev/null +++ b/node_modules/async-mutex/lib/errors.d.ts @@ -0,0 +1,3 @@ +export declare const E_TIMEOUT: Error; +export declare const E_ALREADY_LOCKED: Error; +export declare const E_CANCELED: Error; diff --git a/node_modules/async-mutex/lib/errors.js b/node_modules/async-mutex/lib/errors.js new file mode 100644 index 0000000..c539331 --- /dev/null +++ b/node_modules/async-mutex/lib/errors.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.E_CANCELED = exports.E_ALREADY_LOCKED = exports.E_TIMEOUT = void 0; +exports.E_TIMEOUT = new Error('timeout while waiting for mutex to become available'); +exports.E_ALREADY_LOCKED = new Error('mutex already locked'); +exports.E_CANCELED = new Error('request for lock canceled'); diff --git a/node_modules/async-mutex/lib/index.d.ts b/node_modules/async-mutex/lib/index.d.ts new file mode 100644 index 0000000..df2a2d4 --- /dev/null +++ b/node_modules/async-mutex/lib/index.d.ts @@ -0,0 +1,7 @@ +export { default as Mutex } from './Mutex'; +export { default as MutexInterface } from './MutexInterface'; +export { default as Semaphore } from './Semaphore'; +export { default as SemaphoreInterface } from './SemaphoreInterface'; +export { withTimeout } from './withTimeout'; +export { tryAcquire } from './tryAcquire'; +export * from './errors'; diff --git a/node_modules/async-mutex/lib/index.js b/node_modules/async-mutex/lib/index.js new file mode 100644 index 0000000..0a57479 --- /dev/null +++ b/node_modules/async-mutex/lib/index.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.tryAcquire = exports.withTimeout = exports.Semaphore = exports.Mutex = void 0; +var tslib_1 = require("tslib"); +var Mutex_1 = require("./Mutex"); +Object.defineProperty(exports, "Mutex", { enumerable: true, get: function () { return Mutex_1.default; } }); +var Semaphore_1 = require("./Semaphore"); +Object.defineProperty(exports, "Semaphore", { enumerable: true, get: function () { return Semaphore_1.default; } }); +var withTimeout_1 = require("./withTimeout"); +Object.defineProperty(exports, "withTimeout", { enumerable: true, get: function () { return withTimeout_1.withTimeout; } }); +var tryAcquire_1 = require("./tryAcquire"); +Object.defineProperty(exports, "tryAcquire", { enumerable: true, get: function () { return tryAcquire_1.tryAcquire; } }); +tslib_1.__exportStar(require("./errors"), exports); diff --git a/node_modules/async-mutex/lib/tryAcquire.d.ts b/node_modules/async-mutex/lib/tryAcquire.d.ts new file mode 100644 index 0000000..0341fc0 --- /dev/null +++ b/node_modules/async-mutex/lib/tryAcquire.d.ts @@ -0,0 +1,4 @@ +import MutexInterface from './MutexInterface'; +import SemaphoreInterface from './SemaphoreInterface'; +export declare function tryAcquire(mutex: MutexInterface, alreadyAcquiredError?: Error): MutexInterface; +export declare function tryAcquire(semaphore: SemaphoreInterface, alreadyAcquiredError?: Error): SemaphoreInterface; diff --git a/node_modules/async-mutex/lib/tryAcquire.js b/node_modules/async-mutex/lib/tryAcquire.js new file mode 100644 index 0000000..b975336 --- /dev/null +++ b/node_modules/async-mutex/lib/tryAcquire.js @@ -0,0 +1,12 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.tryAcquire = void 0; +var errors_1 = require("./errors"); +var withTimeout_1 = require("./withTimeout"); +// eslint-disable-next-lisne @typescript-eslint/explicit-module-boundary-types +function tryAcquire(sync, alreadyAcquiredError) { + if (alreadyAcquiredError === void 0) { alreadyAcquiredError = errors_1.E_ALREADY_LOCKED; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (0, withTimeout_1.withTimeout)(sync, 0, alreadyAcquiredError); +} +exports.tryAcquire = tryAcquire; diff --git a/node_modules/async-mutex/lib/withTimeout.d.ts b/node_modules/async-mutex/lib/withTimeout.d.ts new file mode 100644 index 0000000..98912d8 --- /dev/null +++ b/node_modules/async-mutex/lib/withTimeout.d.ts @@ -0,0 +1,4 @@ +import MutexInterface from './MutexInterface'; +import SemaphoreInterface from './SemaphoreInterface'; +export declare function withTimeout(mutex: MutexInterface, timeout: number, timeoutError?: Error): MutexInterface; +export declare function withTimeout(semaphore: SemaphoreInterface, timeout: number, timeoutError?: Error): SemaphoreInterface; diff --git a/node_modules/async-mutex/lib/withTimeout.js b/node_modules/async-mutex/lib/withTimeout.js new file mode 100644 index 0000000..1350a5b --- /dev/null +++ b/node_modules/async-mutex/lib/withTimeout.js @@ -0,0 +1,128 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.withTimeout = void 0; +var tslib_1 = require("tslib"); +/* eslint-disable @typescript-eslint/no-explicit-any */ +var errors_1 = require("./errors"); +function withTimeout(sync, timeout, timeoutError) { + var _this = this; + if (timeoutError === void 0) { timeoutError = errors_1.E_TIMEOUT; } + return { + acquire: function (weightOrPriority, priority) { + var weight; + if (isSemaphore(sync)) { + weight = weightOrPriority; + } + else { + weight = undefined; + priority = weightOrPriority; + } + if (weight !== undefined && weight <= 0) { + throw new Error("invalid weight ".concat(weight, ": must be positive")); + } + return new Promise(function (resolve, reject) { return tslib_1.__awaiter(_this, void 0, void 0, function () { + var isTimeout, handle, ticket, release, e_1; + return tslib_1.__generator(this, function (_a) { + switch (_a.label) { + case 0: + isTimeout = false; + handle = setTimeout(function () { + isTimeout = true; + reject(timeoutError); + }, timeout); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + return [4 /*yield*/, (isSemaphore(sync) + ? sync.acquire(weight, priority) + : sync.acquire(priority))]; + case 2: + ticket = _a.sent(); + if (isTimeout) { + release = Array.isArray(ticket) ? ticket[1] : ticket; + release(); + } + else { + clearTimeout(handle); + resolve(ticket); + } + return [3 /*break*/, 4]; + case 3: + e_1 = _a.sent(); + if (!isTimeout) { + clearTimeout(handle); + reject(e_1); + } + return [3 /*break*/, 4]; + case 4: return [2 /*return*/]; + } + }); + }); }); + }, + runExclusive: function (callback, weight, priority) { + return tslib_1.__awaiter(this, void 0, void 0, function () { + var release, ticket; + return tslib_1.__generator(this, function (_a) { + switch (_a.label) { + case 0: + release = function () { return undefined; }; + _a.label = 1; + case 1: + _a.trys.push([1, , 7, 8]); + return [4 /*yield*/, this.acquire(weight, priority)]; + case 2: + ticket = _a.sent(); + if (!Array.isArray(ticket)) return [3 /*break*/, 4]; + release = ticket[1]; + return [4 /*yield*/, callback(ticket[0])]; + case 3: return [2 /*return*/, _a.sent()]; + case 4: + release = ticket; + return [4 /*yield*/, callback()]; + case 5: return [2 /*return*/, _a.sent()]; + case 6: return [3 /*break*/, 8]; + case 7: + release(); + return [7 /*endfinally*/]; + case 8: return [2 /*return*/]; + } + }); + }); + }, + release: function (weight) { + sync.release(weight); + }, + cancel: function () { + return sync.cancel(); + }, + waitForUnlock: function (weightOrPriority, priority) { + var weight; + if (isSemaphore(sync)) { + weight = weightOrPriority; + } + else { + weight = undefined; + priority = weightOrPriority; + } + if (weight !== undefined && weight <= 0) { + throw new Error("invalid weight ".concat(weight, ": must be positive")); + } + return new Promise(function (resolve, reject) { + var handle = setTimeout(function () { return reject(timeoutError); }, timeout); + (isSemaphore(sync) + ? sync.waitForUnlock(weight, priority) + : sync.waitForUnlock(priority)).then(function () { + clearTimeout(handle); + resolve(); + }); + }); + }, + isLocked: function () { return sync.isLocked(); }, + getValue: function () { return sync.getValue(); }, + setValue: function (value) { return sync.setValue(value); }, + }; +} +exports.withTimeout = withTimeout; +function isSemaphore(sync) { + return sync.getValue !== undefined; +} diff --git a/node_modules/async-mutex/package.json b/node_modules/async-mutex/package.json new file mode 100644 index 0000000..af2f2ba --- /dev/null +++ b/node_modules/async-mutex/package.json @@ -0,0 +1,88 @@ +{ + "name": "async-mutex", + "version": "0.5.0", + "description": "A mutex for guarding async workflows", + "scripts": { + "lint": "eslint src/**/*.ts test/**/*.ts", + "build": "tsc && tsc -p tsconfig.es6.json && tsc -p tsconfig.mjs.json && rollup -o index.mjs mjs/index.js", + "prepublishOnly": "yarn test && yarn build", + "test": "yarn lint && nyc --reporter=text --reporter=html --reporter=lcov mocha test/*.ts", + "coveralls": "cat ./coverage/lcov.info | coveralls" + }, + "author": "Christian Speckner (https://github.com/DirtyHairy/)", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/DirtyHairy/async-mutex" + }, + "prettier": { + "printWidth": 120, + "tabWidth": 4, + "singleQuote": true, + "parser": "typescript" + }, + "importSort": { + ".js, .jsx, .ts, .tsx": { + "style": "eslint", + "parser": "typescript" + } + }, + "eslintConfig": { + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "eqeqeq": "error", + "@typescript-eslint/no-namespace": "off", + "no-async-promise-executor": "off" + } + }, + "keywords": [ + "mutex", + "async" + ], + "files": [ + "lib", + "es6", + "index.mjs" + ], + "devDependencies": { + "@sinonjs/fake-timers": "^11.2.2", + "@types/mocha": "^10.0.6", + "@types/node": "^20.11.25", + "@types/sinonjs__fake-timers": "^8.1.2", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "coveralls": "^3.1.1", + "eslint": "^8.57.0", + "import-sort-style-eslint": "^6.0.0", + "mocha": "^10.3.0", + "nyc": "^15.1.0", + "prettier": "^3.2.5", + "prettier-plugin-import-sort": "^0.0.7", + "rollup": "^4.12.1", + "ts-node": "^10.9.1", + "typescript": "^5.4.2" + }, + "main": "lib/index.js", + "module": "es6/index.js", + "types": "lib/index.d.ts", + "exports": { + ".": { + "import": "./index.mjs", + "require": "./lib/index.js", + "default": "./lib/index.js" + }, + "./package.json": "./package.json" + }, + "dependencies": { + "tslib": "^2.4.0" + } +} diff --git a/package-lock.json b/package-lock.json index 5574a06..68cc5aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@azure/core-auth": "^1.10.1", "@azure/identity": "^4.12.0", "@azure/openai": "^2.0.0", + "async-mutex": "^0.5.0", "axios": "^1.13.2", "body-parser": "^2.2.0", "cors": "^2.8.5", @@ -257,6 +258,15 @@ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", @@ -2030,6 +2040,14 @@ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" }, + "async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "requires": { + "tslib": "^2.4.0" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", diff --git a/package.json b/package.json index 6d8482a..0f95074 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@azure/core-auth": "^1.10.1", "@azure/identity": "^4.12.0", "@azure/openai": "^2.0.0", + "async-mutex": "^0.5.0", "axios": "^1.13.2", "body-parser": "^2.2.0", "cors": "^2.8.5", diff --git a/server2.js b/server2.js index 3892d7c..2979544 100755 --- a/server2.js +++ b/server2.js @@ -874,12 +874,86 @@ app.post("/session", async (req, res) => { const stopFlags = new Map(); + + +const { Mutex } = require('async-mutex'); +const aiChatNum = new Map(); // userId -> number +const mutexes = new Map(); // userId -> Mutex + +function getMutex(userId) { + if (!mutexes.has(userId)) mutexes.set(userId, new Mutex()); + return mutexes.get(userId); +} + +async function setQuota(userId,num) { + const m = getMutex(userId); + return await m.runExclusive(async () => { + console.log("更新用户对话次数:", userId, num); + aiChatNum.set(userId, num); + }); +} + +async function hasQuota(userId) { + const m = getMutex(userId); + return await m.runExclusive(async () => { + console.log("更新用户对话次数:", userId, num); + return aiChatNum.has(userId); + }); +} + +async function getQuota(userId) { + const m = getMutex(userId); + return await m.runExclusive(async () => { + console.log("更新用户对话次数:", userId, num); + return aiChatNum.get(userId); + }); +} + + +async function removeQuota(userId) { + const m = getMutex(userId); + return await m.runExclusive(async () => { + aiChatNum.delete(userId); + }); +} + // ====================== API ====================== +app.post("/updateAiChatNum", async (req, res) => { + const { userId } = req.body; + console.log(userId,98888888888); + if(!hasQuota(userId)){ + return res.status(400).json({ error: "用户不存在" }); + }else{ + // 删除当前内存map存放的可用数量 + removeQuota(userId); + await pool.query( + "update sys_user set ai_chat_num = ? where user_id= ?", + [aiChatNum.get(userId),userId] + ); + res.json({ code: 0, message: "更新成功" }); + } +}) + app.post("/analyze", upload.single("file"), async (req, res) => { try { - - const { requestId } = req.body; + const { userId } = req.body; + if(!hasQuota(userId)){ + const [userRemainChatNum] = await pool.query( + "SELECT ai_chat_num from sys_user where user_id= ?", + [userId] + ); + if (userRemainChatNum.length>0) { + setQuota(userId,userRemainChatNum[0].ai_chat_num); + } + }else{ + if(getQuota(userId)<=0 && getQuota(userId)!==-1){ + return res.status(400).json({ error: "对话次数已用完,请完成账号注册" }); + } + } + + + const { requestId } = req.body; res.setHeader("Content-Type", "text/plain; charset=utf-8"); @@ -907,7 +981,7 @@ app.post("/analyze", upload.single("file"), async (req, res) => { console.log("缺少必要参数"); return res.status(400).json({ error: "缺少必要参数" }); } - const { userPrompt, imageBase64, userId, sessionId } = req.body; + const { userPrompt, imageBase64, sessionId } = req.body; const [rows] = await pool.query( ` SELECT EXISTS( @@ -1171,6 +1245,15 @@ const existingTitle = rows[0].hasTitle === 1; // true / false } console.log("====== 分析完成 ======"); console.timeEnd("analyze"); + + // 更新用户剩余对话次数 + let userChatNum = aiChatNum.get(userId); + + if(userChatNum!==-1){ + userChatNum = Math.max(0, userChatNum - 1); // 防止变成负数 + setQuota(userId,userChatNum); + console.log(`用户剩余AI 对话次数: ${userChatNum}`); + } } catch (err) {