• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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