1.. 2 Copyright (c) 2025 Huawei Device Co., Ltd. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 http://www.apache.org/licenses/LICENSE-2.0 7 Unless required by applicable law or agreed to in writing, software 8 distributed under the License is distributed on an "AS IS" BASIS, 9 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 See the License for the specific language governing permissions and 11 limitations under the License. 12 13######################### 14Concurrency: stdlib 15######################### 16 17******** 18Overview 19******** 20 21In general we have several directions for concurrency in |LANG| stdlib: 22 23- interfaces for coroutines 24- concurrency/async primitives required for the compatibility with JS/TS 25- support for the OHOS-specific concurrency features(such as TaskPool, AsyncLock, etc.) 26- new concurrent containers/concurrency primitives 27 28************************* 29Interfaces for coroutines 30************************* 31 32.. _Concurrency Job: 33 34=== 35Job 36=== 37 38Job is the class which represent the job/task executing on the coroutine. 39 40Job object represents the job which will be scheduled and executed. There is no guarantee that it will be executed on the same thread when it was created. 41 42If coroutine completes normally, it puts its return value to the linked Job instance and marks the Job as successfully completed. 43 44If the during execution job function throws error this error will be propagated to the `Await` method and rethrown. 45 46In general Job class have these public API: 47 48.. code-block:: typescript 49 50 class Job<T> { 51 constructor(v: T) { 52 this.val = v 53 } 54 55 // Awaits execution of the job. 56 // On success return value, on fail the Error will be thrown 57 Await() : T { 58 // ... 59 } 60 } 61 62------------------ 63Exception handling 64------------------ 65 66The exception thrown during execution can be handled at the `Await` via try-catch block. 67 68In case if exception thrown, but `Await` method never called - an UnhandledExceptionHandler should be called upon the whole program completion. 69 70.. _Concurrency launch: 71 72====== 73launch 74====== 75 76For creation of the coroutine you should use standard library function ``launch`` with this signature(Note: final inteface is still will be defined, current is just to demonstrate semantic): 77 78.. code-block:: typescript 79 80 function launch<R, F extends CoroFun<never, R>>() { 81 coroFun: F, 82 ..args: (Object | null | undefined)[] 83 ) : Job<R> 84 85 86Where `coroFun` is either lambda, either static method or function. 87 88The created coroutine will be scheduled on the one of the coroutine worker threads and can be rescheduled to another later. 89 90======== 91Schedule 92======== 93 94The `Schedule` is the method of `Coroutine` class which notifies Scheduler that current coroutine could be suspended at this moment, 95so if it is non-empty execution queue - current coroutine will be suspended and best suspended coroutine will be scheduled on current coroutine worker. 96 97*************************************** 98JS/TS compatible concurrency primitives 99*************************************** 100 101.. _Concurrency Promise Class: 102 103======= 104Promise 105======= 106 107The Promise object is introduced for the support asynchronous API. It is the object which represents a proxy for the result of the asynchronous operation. The semantic of the Promise is similar to the semantic of Promise in JS/TS if it used in the context of one coroutine. 108 109Promise object represents the values returned by call of async function. 110It belongs to the core packages of the standard library, 111and thus it is imported by default and may be used 112without any qualification. 113 114Promise lifetime is not limited to the lifetime of the root coroutine when it was created. 115 116In general promise not designed to be used concurrently simultaneously from multiple coroutines. But it is safe to do this: 117 118- pass promise from one coroutine to another and do not use it anymore in original coroutine 119- pass promise from one coroutine to another, use it in both coroutines but call `then` only in one coroutine 120- pass promise from one coroutine to another, use it in both coroutines, call `then` in both coroutines, but user provide custom synchronization which guarantee that `then` will not be called simultaneously for this promise 121 122The following methods are used as follows: 123 124- ``then`` takes two arguments (the first argument is the callback used if the 125 promise is fulfilled, and the second if it is rejected), and returns 126 ``Promise<U>``. If ``then`` called from same parent coroutine multiple times - the order of ``then`` is the same if it is called in JS/TS. 127 The callback is called on the coroutine when ``then`` called, so if you pass promise from one coroutine to another 128 and called ``then`` in both - they will be called in different coroutines and maybe concurrently, so developer should 129 take care about possible data races. 130 131.. index:: 132 class 133 value 134 launch 135 argument 136 callback 137 package 138 standard library 139 method 140 141.. 142 Promise<U>::then<U, E = never>(onFulfilled: ((value: T) => U|PromiseLike<U> throws)|undefined, onRejected: ((error: NullishType) => E|PromiseLike<E> throws)|undefined): Promise<U|E> 143 144.. code-block:: typescript 145 146 Promise<U>::then<U, E = never>(onFulfilled: ((value: T) => U|PromiseLike<U> throws)|undefined, onRejected: ((error: NullishType) => E|PromiseLike<E> throws)|undefined): Promise<U|E> 147 148- ``catch`` takes one argument(the callback called after promise is rejected) and returns ``Promise<U|T>`` 149 150.. code-block-meta: 151 152.. code-block:: typescript 153 154 Promise<U>::catch<U = never>(onRejected?: (error: NullishType) => U|PromiseLike<U> throws): Promise<T|U> 155 156- ``finally`` takes one argument (the callback called after ``promise`` is 157 either fulfilled or rejected) and returns ``Promise<T>``. 158 159.. index:: 160 alias 161 callback 162 call 163 164.. code-block:: typescript 165 166 finally(onFinally?: () => void throws): Promise<T> 167 168 169--------------------------- 170Unhandled rejected promises 171--------------------------- 172 173In case of unhandled rejection of promise either custom handler provided for promise rejection and it will be called, 174either default promise rejection handler will be called upon the whole program completion. 175 176**************************** 177Concurrency extensions 178**************************** 179 180Besides JS/TS compatible concurrency primitives, there are some extensions in |LANG| which introduce some additional concurrency functionality. 181 182======== 183TaskPool 184======== 185 186TaskPool provides multi-threaded environments for applications. It helps to run sequence of tasks on the pool of threads. Also you shouldn't care about managing this pool: the TaskPool itself manage lifetime of threads in the pool, their number, etc. 187 188TaskPool allows to reduce resource consumption and improve system performance. 189 190----------------------- 191TaskPool for JS context 192----------------------- 193 194TaskPool could be used in JS context and in the static context. 195 196The unit of execution in TaskPool is concurrent function (function with @Concurrent decorator for JS/TS compatible mode, for M:N mode this decorator is optional). 197 198We have some limitations for the TaskPool used in JS context: 199 200* functions used as tasks in TaskPool should be defined with @Concurrent decorator 201* it is not allowed to use closure variables in @Concurrent function 202 203--------------------------- 204TaskPool for static context 205--------------------------- 206 207In static context we have same API as for JS context to the language syntax/semantic extent and we don't have any specific requirements for functions used as tasks 208except one requirement which is applicable for all M:N coroutines: we shouldn't have interop in this function. 209 210In general TaskPool provides structured concurrency features. I.e. it allow you to start some set of tasks, cancel task, wait for tasks, etc. 211 212The tasks are executed on the Coroutine Workers. The cross-language interoperability is forbidden in tasks. 213 214For detailed information about TaskPool please take a look at standard library documentation. 215 216Experimental: it is an option to use EACoroutines for the TaskPool, in this case it is allowed to use cross-language interoperability in the Tasks. 217 218========= 219AsyncLock 220========= 221 222For objects shared between different concurrent domains, it is crucial to have some machinery to provide some machinery for synchronization. One of the ways to guarantee thread-safe access to the object is Locking machinery. For this we introduce AsyncLock in |LANG|. 223 224For languages with coroutines which are executing on the more than one CPU core we may need such synchronization primitive as Lock. But we can't use OS-level lock, since there are queue of coroutines waiting for execution on this core. 225 226For this we need introduce special type of lock, which will not block the whole OS-level thread on such Lock. 227 228.. uml:: os_based_lock_deadlock_seq.plantuml 229 230In Java language we have `synchronized` methods for guarantee that only one thread executing such method. For |LANG| we can introduce special class `AsyncLock`, which have method `async` for running code 231 232.. code-block:: ts 233 :linenos: 234 235 class AsyncLock { 236 async(lambda:any) { 237 // acquire lock 238 lambda(); 239 // release lock 240 } 241 } 242 243The semantic of such lock should be something like this: 244 245.. code-block:: c++ 246 :linenos: 247 248 void Lock(ObjectHeader* obj) { 249 while (1) { 250 if (try_lock(obj) == SUCCESS) { 251 return; 252 } 253 yield(); // suspend current coroutine 254 } 255 } 256 257 bool try_lock(ObjectHeader* obj) { 258 if (obj.SetState(LOCKED) == SUCCESS) { 259 return SUCCESS; 260 } 261 return FAIL; 262 } 263 264 265For this it is enough to have special state in `ObjectHeader` and change it atomically. Or we can have just some atomic field `state`. 266 267But `while (1)` can be optimized if we will have explicit scheduler for such tasks. For example we can group locked coroutines by lock object, and have queue for unlock events, when we process something from this queue, we can add next coroutine from this queue to the queue for scheduler. 268 269Example with `AsyncLock` usage: 270 271.. code-block:: javascript 272 :linenos: 273 274 import {AsyncLock, SyncMode} from '@ohos.sync'; 275 276 // @sendable 277 class Demo { 278 count: number = 0 279 lock: AsyncLock = new AsyncLock(); 280 async add() { 281 this.lock.async(lock => { 282 this.count++; 283 }) 284 } 285 async get() { 286 this.lock.async(lock => { 287 return this.count; 288 }) 289 } 290 } 291 292 293For VMs without shared memory, however, the implementation of AsyncLock requires different approach. Since we can't share object, we can obtain lock object by name or id from different threads. And the lock object should be accessible from any VM instance. The same approach applicable for the VM with shared memory. 294 295 296 297.. 298 /** 299 * Information about all lock operations on the AsyncLock instance. 300 * 301 * @syscap SystemCapability.Utils.Lang 302 * @crossplatform 303 * @atomicservice 304 * @since 12 305 */ 306 class AsyncLockState { 307 /** 308 * Array of lock operations which held the lock. 309 * 310 * @syscap SystemCapability.Utils.Lang 311 * @crossplatform 312 * @atomicservice 313 * @since 12 314 */ 315 held: AsyncLockInfo[]; 316 /** 317 * Array of lock operations waiting for the lock. 318 * 319 * @syscap SystemCapability.Utils.Lang 320 * @crossplatform 321 * @atomicservice 322 * @since 12 323 */ 324 pending: AsyncLockInfo[]; 325 } 326 /** 327 * Information about a lock and a lock operation. 328 * 329 * @syscap SystemCapability.Utils.Lang 330 * @crossplatform 331 * @atomicservice 332 * @since 12 333 */ 334 class AsyncLockInfo { 335 /** 336 * Identifier of the lock if the lock is anonymous. For named locks this field is undefined 337 * 338 * @syscap SystemCapability.Utils.Lang 339 * @crossplatform 340 * @atomicservice 341 * @since 12 342 */ 343 id?: number; 344 /** 345 * Name of the named lock. For anonymous locks this field is undefined. 346 * 347 * @syscap SystemCapability.Utils.Lang 348 * @crossplatform 349 * @atomicservice 350 * @since 12 351 */ 352 name?: string; 353 /** 354 * Lock operation's mode. 355 * 356 * @syscap SystemCapability.Utils.Lang 357 * @crossplatform 358 * @atomicservice 359 * @since 12 360 */ 361 mode: AsyncLockMode; 362 /** 363 * lockAsync caller thread's identifier. 364 * 365 * @syscap SystemCapability.Utils.Lang 366 * @crossplatform 367 * @atomicservice 368 * @since 12 369 */ 370 threadId: number; 371 } 372 /** 373 * Object to abort an async operation. 374 * 375 * @syscap SystemCapability.Utils.Lang 376 * @crossplatform 377 * @atomicservice 378 * @since 12 379 */ 380 class AbortSignal<T> { 381 /** 382 * Set to true to abort an operation 383 * 384 * @syscap SystemCapability.Utils.Lang 385 * @crossplatform 386 * @atomicservice 387 * @since 12 388 */ 389 aborted: boolean; 390 391 /** 392 * Reason of the abort. This value will be used to reject the promise returned from lockAsync. 393 * 394 * @syscap SystemCapability.Utils.Lang 395 * @crossplatform 396 * @atomicservice 397 * @since 12 398 */ 399 reason: T 400 } 401 402=========================== 403AsyncLock Deadlock Detector 404=========================== 405 406It is possible that the developer make a mistake and create code which lead to the deadlock situation while using AsyncLock. For this it is possible to specify the maximum time which we expect is enough for successful Lock acquirence. In case if we reach the limit - the provided by developer callback will be called. 407