目录

Function Flow Runtime Development

Overview

Function Flow Runtime (FFRT) is a task-based and data-driven concurrent programming model that allows you to develop an application by creating tasks and describing their dependencies.

Specifically, FFRT automatically and concurrently schedules and executes tasks of the application based on the task dependency status and available resources, so that you can focus on feature development.

This document provides guidance for you to implement concurrent programming based on the FFRT programming model.

Maintenance and Test

[h2]Timeout Monitoring

FFRT provides a queue-level and task-level timeout maintenance and test mechanism to monitor the end-to-end time for scheduling queues and tasks that carry important responsibilities in user services.

The callback function executed when a task times out must be unique in the process and should be configured in the FFRT by the service party before the task is submitted. The callback function cannot be configured during task submission or task timeout detection.

The APIs are as follows:

C++ API C API Description
queue_attr::timeout ffrt_queue_attr_set_timeout Sets the queue timeout.
queue_attr::callback ffrt_queue_attr_set_callback Sets the queue timeout callback.

[h2]Long-Time Task Monitoring

Mechanism

Example

Search for the keyword RecordSymbolAndBacktrace in the corresponding process log. The following is an example of the corresponding log:

W C01719/ffrt: 60500:RecordSymbolAndBacktrace:159 Tid[16579] function occupies worker for more than [1]s.
W C01719/ffrt: 60501:RecordSymbolAndBacktrace:164 Backtrace:
W C01719/ffrt: #00 pc 00000000000075f0 /system/lib64/module/file/libhash.z.so
W C01719/ffrt: #01 pc 0000000000008758 /system/lib64/module/file/libhash.z.so
W C01719/ffrt: #02 pc 0000000000012b98 /system/lib64/module/file/libhash.z.so
W C01719/ffrt: #03 pc 000000000002aaa0 /system/lib64/platformsdk/libfilemgmt_libn.z.so
W C01719/ffrt: #04 pc 0000000000054b2c /system/lib64/platformsdk/libace_napi.z.so
W C01719/ffrt: #05 pc 00000000000133a8 /system/lib64/platformsdk/libuv.so
W C01719/ffrt: #06 pc 00000000000461a0 /system/lib64/chipset-sdk/libffrt.so
W C01719/ffrt: #07 pc 0000000000046d44 /system/lib64/chipset-sdk/libffrt.so
W C01719/ffrt: #08 pc 0000000000046a6c /system/lib64/chipset-sdk/libffrt.so
W C01719/ffrt: #09 pc 00000000000467b0 /system/lib64/chipset-sdk/libffrt.so

The log prints the task stack, Worker thread ID, and execution time of the long-time task. Find the corresponding component based on the stack to determine the blocking cause.

Precautions

N/A

[h2]Running Information Dump

Mechanism

FFRT provides an external interface API ffrt_dump to dump the internal information about the running of the FFRT subsystem, including:

  1. FFRT statistics: number of submitted tasks, number of running tasks, number of coroutine switching times, and number of completed tasks;

  2. Worker thread information: number of Worker threads in each QoS, Worker ID, ID of the running task, task name, and task type.

  3. Common task information: common tasks that are not released in the current process, dump task name and ID, and call stack information of each task.

  4. Queue task information: queue tasks that are not released in the current process, dump task name and ID, and call stack information of each task.

When the current process is frozen, the DFX module proactively calls the ffrt_dump API to dump the FFRT information to the freeze file and store the file in the /data/log/faultlog/faultlogger/ directory. You can directly use the task call stack information in the file to locate the frame freezing of the corresponding task.

Example

ready task ptr: qos 0 readptr 79 writeptr 79
ready task ptr: qos 1 readptr 360 writeptr 360
ready task ptr: qos 2 readptr 19 writeptr 19
ready task ptr: qos 3 readptr 0 writeptr 0
ready task ptr: qos 4 readptr 0 writeptr 0
ready task ptr: qos 5 readptr 65 writeptr 65
ready task ptr: qos 6 readptr 0 writeptr 0
ready task ptr: qos 7 readptr 0 writeptr 0
submit queue: readptr 24 writeptr 24
intr wake: status 255
proc status: taskCnt 23 vercnt 0sigCnt0
    |-> worker count
        qos 0: worker num:1 tid:31676
        qos 2: worker num:3 tid:51349, 28769, 28565
        qos 5: worker num:1 tid:30605
    |-> worker status
        qos 0: worker tid 31676 is running nothing
        qos 2: worker tid 51349 is running nothing
        qos 2: worker tid 28769 is running, task id 24591 name sq_CesSrvMain_12_PublishCommonEventDetailed_24591 fromTid 43928 createTime 2024-11-27 02:52:27.325248 executeTime 2024-11-27 02:52:27.326150
        qos 2: worker tid 28565 is running, task id 24611 name sq_dfx_freeze_task_queue_16_NotifyAppFaultTask_24611 fromTid 43833 createTime 2024-11-27 02:52:38.114787 executeTime 2024-11-27 02:52:38.115424
        qos 5: worker tid 30605 is running, task id 24595 name sq_AbilityManagerService_19_SubmitTaskInner_24595 fromTid 43610 createTime 2024-11-27 02:52:27.844237 executeTime 2024-11-27 02:52:27.844573
    |-> ready queue status
    |-> blocked by task dependence
        <1/1>stack: task id 3,qos 2,name AgingTask fromTid 43417 createTime 2024-11-27 01:21:39.641673 executeTime 2024-11-27 01:21:39.642290
#00 pc 0000000000065c5c /system/lib64/ndk/libffrt.so(CoYield()+560)(22be57f01a789a03813d26a19c3a4042)
#01 pc 00000000000a3268 /system/lib64/ndk/libffrt.so(ffrt::this_task::SleepUntilImpl(std::__h::chrono::time_point<std::__h::chrono::steady_clock, std::__h::chrono::duration<long long, std::__h::ratio<1l, 1000000000l>>> const&)+356)(22be57f01a789a03813d26a19c3a4042)
#02 pc 00000000000a39b4 /system/lib64/ndk/libffrt.so(ffrt_usleep+60)(22be57f01a789a03813d26a19c3a4042)
#03 pc 0000000000420de0 /system/lib64/libbms.z.so(2eb52bd03af1b9a31e14ffe60bfc39da)
#04 pc 00000000000a6a2c /system/lib64/ndk/libffrt.so(ffrt::CPUEUTask::Execute()+300)(22be57f01a789a03813d26a19c3a4042)
#05 pc 0000000000066d18 /system/lib64/ndk/libffrt.so(22be57f01a789a03813d26a19c3a4042)

Precautions

The DFX module has requirements on the processing time during freeze. There is a low probability that the information collected by ffrt_dump is incomplete and the freeze processing time expires. In this case, the information flushed to the disk is missing.

[h2]Blackbox Logs

Mechanism

When a process crashes, the FFRT module receives signals (SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSTKFLT, SIGSTOP, SIGSYS, and SIGTRAP) and saves important running information to the faultlog, including the running task, running information and call stack information of the current Worker thread, common task information, and queue task information. You can use the information to locate the crashes.

Example

C01719/CameraDaemon/ffrt: 9986:operator():254 <<<=== ffrt black box(BBOX) start ===>>>
C01719/CameraDaemon/ffrt: 9987:SaveCurrent:63 <<<=== current status ===>>>
C01719/CameraDaemon/ffrt: 9988:SaveCurrent:68 signal SIGABRT triggered: source tid 5962, task id 17, qos 2, name SvrWatchdog
C01719/CameraDaemon/ffrt: 9989:SaveWorkerStatus:94 <<<=== worker status ===>>>
C01719/CameraDaemon/ffrt: 9990:SaveWorkerStatus:100 qos 0: worker tid 6410 is running nothing
C01719/CameraDaemon/ffrt: 9991:SaveWorkerStatus:100 qos 2: worker tid 5968 is running nothing
C01719/CameraDaemon/ffrt: 9992:SaveWorkerStatus:100 qos 2: worker tid 5964 is running nothing
C01719/CameraDaemon/ffrt: 9993:SaveWorkerStatus:100 qos 2: worker tid 5963 is running nothing
C01719/CameraDaemon/ffrt: 9994:SaveWorkerStatus:105 qos 2: worker tid 5962 is running task id 17 name SvrWatchdog
C01719/CameraDaemon/ffrt: 9995:SaveWorkerStatus:100 qos 2: worker tid 5967 is running nothing
C01719/CameraDaemon/ffrt: 9996:SaveWorkerStatus:100 qos 2: worker tid 5965 is running nothing
C01719/CameraDaemon/ffrt: 9997:SaveWorkerStatus:100 qos 2: worker tid 5961 is running nothing
C01719/CameraDaemon/ffrt: 9998:SaveWorkerStatus:100 qos 2: worker tid 1146 is running nothing
C01719/CameraDaemon/ffrt: 9999:SaveWorkerStatus:100 qos 2: worker tid 1145 is running nothing
C01719/CameraDaemon/ffrt: 10000:SaveWorkerStatus:100 qos 2: worker tid 5966 is running nothing

Precautions

N/A

[h2]Tracing

Mechanism

During FFRT task scheduling and execution, the system traces the task status in the FFRT framework in real time. You can use the trace graphical tool to analyze whether the task behavior meets the expectation.

Example

  1. Starting trace capture

    hdc shell "hitrace -t 10 -b 20480 -o /data/local/tmp/in_systrace.ftrace sched freq idle ffrt"
    # -t: specifies the trace collection duration, during which all trace records are flushed to the disk.
    # -b: specifies the size of the trace record cache. If the buffer is insufficient, some records may be overwritten and not flushed to disks.
    # -o: specifies the path for storing trace files.
  2. Using a graphical tool

    Obtain the trace file from the device and use a graphical tool, for example, Perfetto, to analyze the file.

Precautions

You can also add traces to your service code to locate the fault. Note that in the high-frequency call process, adding traces will cause system overhead and affect service performance.

[h2]Debug Logs

Mechanism

Example

4190  5631 D C01719/neboard:EngineServiceAbility:1/ffrt: 275337:Detach:147 qos 3 thread not joinable
3257  6075 D C01719/com.ohos.sceneboard/ffrt: 513070:SetDefaultThreadAttr:148 qos apply tid[6075] level[3]

Precautions

The FFRT is the system base and supports the running of a large number of upper-layer services and frameworks. If the debug log function is enabled globally, the number of logs will exceed the threshold, which affects the log output of other modules.

How to Develop

The following describes how to use the native APIs provided by FFRT to create parallel tasks and serial queue tasks and destroy corresponding resources.

  1. Add a dynamic link library to the CMakeLists.txt project.

    libffrt.z.so
  2. Include the following header files in the project.

    #include "ffrt/task.h"
    #include "ffrt/mutex.h"
    #include "ffrt/shared_mutex.h"
    #include "ffrt/condition_variable.h"
    #include "ffrt/sleep.h"
    #include "ffrt/queue.h"
    #include "ffrt/loop.h"
    #include "ffrt/timer.h"
  3. Encapsulate the function to be executed.

    // Method 1: Use the template. C++ is supported.
    template<class T>
    struct function {
        ffrt_function_header_t header;
        T closure;
    };
    
    template<class T>
    void exec_function_wrapper(void* t)
    {
        auto f = reinterpret_cast<function<std::decay_t<T>>*>(t);
        f->closure();
    }
    
    template<class T>
    void destroy_function_wrapper(void* t)
    {
        auto f = reinterpret_cast<function<std::decay_t<T>>*>(t);
        f->closure = nullptr;
    }
    
    template<class T>
    inline ffrt_function_header_t* create_function_wrapper(T&& func,
        ffrt_function_kind_t kind = ffrt_function_kind_general)
    {
        using function_type = function<std::decay_t<T>>;
        static_assert(sizeof(function_type) <= ffrt_auto_managed_function_storage_size,
            "size of function must be less than ffrt_auto_managed_function_storage_size");
    
        auto p = ffrt_alloc_auto_managed_function_storage_base(kind);
        auto f = new (p)function_type;
        f->header.exec = exec_function_wrapper<T>;
        f->header.destroy = destroy_function_wrapper<T>;
        f->closure = std::forward<T>(func);
        return reinterpret_cast<ffrt_function_header_t*>(f);
    }
    
    // Method 2
    typedef struct {
        ffrt_function_header_t header;
        ffrt_function_t func;
        ffrt_function_t after_func;
        void* arg;
    } ffrt_function_wrapper_t;
    
    static inline void ffrt_exec_function_wrapper(void* t)
    {
       ffrt_function_wrapper_t* f = (ffrt_function_wrapper_t *)t;
       if (f->func) {
           f->func(f->arg);
       }
    }
    
    static inline void ffrt_destroy_function_wrapper(void* t)
    {
        ffrt_function_wrapper_t* f = (ffrt_function_wrapper_t *)t;
        if (f->after_func) {
            f->after_func(f->arg);
        }
    }
    
    #define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1])
    static inline ffrt_function_header_t *ffrt_create_function_wrapper(ffrt_function_t func,
        ffrt_function_t after_func, void* arg, ffrt_function_kind_t kind)
    {
        FFRT_STATIC_ASSERT(sizeof(ffrt_function_wrapper_t) <= ffrt_auto_managed_function_storage_size,
            size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size);
    
        ffrt_function_wrapper_t* f = (ffrt_function_wrapper_t *)ffrt_alloc_auto_managed_function_storage_base(kind);
        f->header.exec = ffrt_exec_function_wrapper;
        f->header.destroy = ffrt_destroy_function_wrapper;
        f->func = func;
        f->after_func = after_func;
        f->arg = arg;
        return (ffrt_function_header_t *)f;
    }
    
    // Example: function to be submitted for execution.
    void OnePlusForTest(void* arg)
    {
        (*static_cast<int*>(arg)) += 1;
    }
  4. Set task attributes, including the QoS level and task name.

    // ******Initialize the attributes of the parallel task******
    ffrt_task_attr_t attr;
    ffrt_task_attr_init(&attr);
    
    // ******Create a serial queue******
    
    // Create the attributes of the serial queue.
    ffrt_queue_attr_t queue_attr;
    // Create the handle to the serial queue.
    ffrt_queue_t queue_handle;
    
    // Initialize the queue attribute.
    (void)ffrt_queue_attr_init(&queue_attr);
    
    // Set the QoS if necessary.
    ffrt_queue_attr_set_qos(&queue_attr, static_cast<ffrt_qos_t>(ffrt_qos_inherit));
    // Set the timeout period (ms) if necessary.
    ffrt_queue_attr_set_timeout(&queue_attr, 10000);
    // Set the timeout callback if necessary.
    int x = 0;
    ffrt_queue_attr_set_callback(&queue_attr, ffrt_create_function_wrapper(OnePlusForTest, NULL, &x,
        ffrt_function_kind_queue));
    
    // Initialize the queue based on the attributes.
    queue_handle = ffrt_queue_create(ffrt_queue_serial, "test_queue", &queue_attr);
  5. Submit the task.

    int a = 0;
    // ******Parallel task******
    // Submit the parallel task without obtaining a handle.
    ffrt_submit_base(
        ffrt_create_function_wrapper(OnePlusForTest, NULL, &a, ffrt_function_kind_general), NULL, NULL, &attr);
    // Submit the parallel task and obtain a handle.
    ffrt_task_handle_t task = ffrt_submit_h_base(
        ffrt_create_function_wrapper(OnePlusForTest, NULL, &a, ffrt_function_kind_general), NULL, NULL, &attr);
    
    // ******Serial queue task******
    // Submit the serial queue task without obtaining a handle.
    ffrt_queue_submit(queue_handle,
        ffrt_create_function_wrapper(OnePlusForTest, NULL, &a, ffrt_function_kind_queue), NULL);
    // Submit the serial queue task and obtain a handle.
    ffrt_task_handle_t handle = ffrt_queue_submit_h(queue_handle,
        ffrt_create_function_wrapper(OnePlusForTest, NULL, &a, ffrt_function_kind_queue), NULL);
    
    // Call wait if you need to wait for the execution result.
    const std::vector<ffrt_dependence_t> wait_deps = {{ffrt_dependence_task, task}};
    ffrt_deps_t wait{static_cast<uint32_t>(wait_deps.size()), wait_deps.data()};
    ffrt_wait_deps(&wait);
    
    ffrt_queue_wait(handle);
  6. (Optional) If a task does not need to be destroyed, you can submit the task through a simplified API.

    int a = 0;
    // If the after_func function pointer in step 3 is NULL, you can use the simplified API to submit a task to avoid redundant task structure encapsulation.
    // ******Parallel task******
    // Submit a parallel task without a handle through a simplified API.
    ffrt_submit_f(OnePlusForTest, &a, NULL, NULL, &attr);
    // Submit a parallel task with a handle through a simplified API.
    ffrt_task_handle_t task = ffrt_submit_h_f(OnePlusForTest, &a, NULL, NULL, &attr);
    
    // ******Serial queue task******
    // Submit a serial queue task that does not return a handle through a simplified API.
    ffrt_queue_submit_f(queue_handle, OnePlusForTest, &a, NULL);
    // Submit a serial queue task with a handle through a simplified API.
    ffrt_task_handle_t handle = ffrt_queue_submit_h_f(queue_handle, OnePlusForTest, &a, NULL);
    
    // Call wait if you need to wait for the execution result.
    const std::vector<ffrt_dependence_t> wait_deps = {{ffrt_dependence_task, task}};
    ffrt_deps_t wait{static_cast<uint32_t>(wait_deps.size()), wait_deps.data()};
    ffrt_wait_deps(&wait);
    
    ffrt_queue_wait(handle);
  7. Destroy the resources after the task is submitted.

    // ******Destroy the parallel task******
    ffrt_task_attr_destroy(&attr);
    ffrt_task_handle_destroy(task);
    
    // ******Destroy the serial queue task******
    // Destroy the task handle and then the queue.
    ffrt_queue_attr_destroy(&queue_attr);
    ffrt_task_handle_destroy(handle);
    ffrt_queue_destroy(queue_handle);

Suggestions

[h2]Suggestion 1: Functional Programming

Using pure functions helps you maximize the parallelism and avoid data races and lock abuse.

In practice, you may not use pure functions in certain scenarios, with the following prerequisites:

[h2]Suggestion 2: Using FFRT APIs

[h2]Suggestion 3: Deadline Mechanism

[h2]Suggestion 4: Migration from the Thread Model

Constraints

[h2]Thread Local Variables

Risks exist when thread local variables are used in FFRT tasks. The details are as follows:

[h2]Thread Binding

[h2]Synchronization Primitives in the Standard Library

Using the standard library's recursive mutex in FFRT tasks may lead to deadlocks. It is necessary to replace it with the recursive mutex provided by FFRT. The details are as follows:

[h2]Synchronization Primitives in FFRT

[h2]Support for Process fork()

[h2]Dynamic FFRT Deployment

[h2]Limited Number of Input and Output Dependencies

[h2]Restrictions on Process or Thread Exit

Common Anti-Patterns

[h2]Responsibility for Nulling and Destroying FFRT Objects After Initialization in the C API

[h2]Incorrect Variable Lifecycle

Using FFRT in DevEco Studio

[h2]Using FFRT C API

Native Development Kit (NDK) is a toolset provided by the system. It offers native APIs that allow you to implement key application functions using C or C++ code.

The FFRT C APIs have been integrated into the NDK. You can directly use the corresponding API in DevEco Studio.

#include "ffrt/task.h"
#include "ffrt/mutex.h"
#include "ffrt/shared_mutex.h"
#include "ffrt/condition_variable.h"
#include "ffrt/sleep.h"
#include "ffrt/queue.h"
#include "ffrt/loop.h"
#include "ffrt/timer.h"
#include "ffrt/type_def.h"

[h2]Using FFRT C++ API

The FFRT deployment depends on the FFRT dynamic library libffrt.so and a group of header files. The dynamic library exports only C APIs, and C++ APIs call C APIs. In addition, C++ elements in APIs are compiled into the dynamic library based on the header files, ensuring ABI compatibility.

To use FFRT C++ APIs, you need to use the third-party library @ppd/ffrt, which is officially maintained by FFRT.

Run the following command in the module directory to install the third-party library:

ohpm install @ppd/ffrt

You can also configure the dependencies in the oh-package.json5 file so that the third-party library can be automatically downloaded and installed through DevEco Studio.

Add the header file search path and link dependency to the CMakeLists.txt file.

# ${MODULES_PATH} indicates the installation path of the third-party library. You need to define the path or replace it with an absolute or relative path.
include_directories(${MODULES_PATH}/@ppd/ffrt/include)
target_link_libraries(${TARGET_NAME} PUBLIC libffrt.z.so)

Use the FFRT C++ API in the code.

// include all C or C++ header files
#include "ffrt/ffrt.h"

// include specified header files
#include "ffrt/cpp/task.h"
#include "ffrt/cpp/mutex.h"
#include "ffrt/cpp/shared_mutex.h"
#include "ffrt/cpp/condition_variable.h"
#include "ffrt/cpp/sleep.h"
#include "ffrt/cpp/queue.h"