bsp-microej-async-worker  0.2.1
bsp-microej-async-worker
Data Structures | Macros | Typedefs | Enumerations | Functions
microej_async_worker.h File Reference

Asynchronous Worker API. This library helps writing SNI functions that must be executed asynchronously. More...

#include <stdint.h>
#include "sni.h"
#include "osal.h"

Go to the source code of this file.

Data Structures

struct  MICROEJ_ASYNC_WORKER_job
 A job to execute in a worker. More...
 
struct  MICROEJ_ASYNC_WORKER_handle_t
 An async worker. More...
 

Macros

#define MICROEJ_ASYNC_WORKER_worker_declare(_name, _job_count, _param_type, _waiting_list_size)
 Declares a worker named _name. More...
 

Typedefs

typedef struct MICROEJ_ASYNC_WORKER_job MICROEJ_ASYNC_WORKER_job_t
 See struct MICROEJ_ASYNC_WORKER_job.
 
typedef void(* MICROEJ_ASYNC_WORKER_action_t) (MICROEJ_ASYNC_WORKER_job_t *job)
 Pointer to a function to call asynchronously.
 

Enumerations

enum  MICROEJ_ASYNC_WORKER_status_t { MICROEJ_ASYNC_WORKER_OK, MICROEJ_ASYNC_WORKER_ERROR, MICROEJ_ASYNC_WORKER_INVALID_ARGS }
 Return codes list.
 

Functions

MICROEJ_ASYNC_WORKER_status_t MICROEJ_ASYNC_WORKER_initialize (MICROEJ_ASYNC_WORKER_handle_t *async_worker, uint8_t *name, OSAL_task_stack_t stack, int32_t priority)
 Initializes and starts a worker previously declared with MICROEJ_ASYNC_WORKER_worker_declare() macro. More...
 
MICROEJ_ASYNC_WORKER_job_tMICROEJ_ASYNC_WORKER_allocate_job (MICROEJ_ASYNC_WORKER_handle_t *async_worker, SNI_callback sni_retry_callback)
 Allocates a new job for the given worker. More...
 
MICROEJ_ASYNC_WORKER_status_t MICROEJ_ASYNC_WORKER_free_job (MICROEJ_ASYNC_WORKER_handle_t *async_worker, MICROEJ_ASYNC_WORKER_job_t *job)
 Frees a job previously allocated with MICROEJ_ASYNC_WORKER_allocate_job(). More...
 
MICROEJ_ASYNC_WORKER_status_t MICROEJ_ASYNC_WORKER_async_exec (MICROEJ_ASYNC_WORKER_handle_t *async_worker, MICROEJ_ASYNC_WORKER_job_t *job, MICROEJ_ASYNC_WORKER_action_t action, SNI_callback on_done_callback)
 Executes the given job asynchronously. More...
 
MICROEJ_ASYNC_WORKER_status_t MICROEJ_ASYNC_WORKER_async_exec_no_wait (MICROEJ_ASYNC_WORKER_handle_t *async_worker, MICROEJ_ASYNC_WORKER_job_t *job, MICROEJ_ASYNC_WORKER_action_t action)
 Executes the given job asynchronously. More...
 
MICROEJ_ASYNC_WORKER_job_tMICROEJ_ASYNC_WORKER_get_job_done (void)
 Returns the job that has been executed. More...
 

Detailed Description

Asynchronous Worker API. This library helps writing SNI functions that must be executed asynchronously.

The execution of an SNI function prevents the other Java thread to be scheduled. That's why blocking native functions or long native functions should be executed asynchronously.

An async worker is declared statically using the MICROEJ_ASYNC_WORKER_worker_declare() macro and started with the MICROEJ_ASYNC_WORKER_initialize() function. Jobs are allocated using MICROEJ_ASYNC_WORKER_allocate_job() and scheduled with MICROEJ_ASYNC_WORKER_async_exec().

Typical usage consists in declaring:

For example, if we have to implement asynchronously the following two SNI functions :

int foo(int i, int j);
void bar(int i);

Then we will declare the async worker as following:

// foo() parameters structure
typedef struct {
int i;
int j;
int result;
} foo_param_t;
// bar() parameters structure
typedef struct {
int i;
} bar_param_t;
// union of all the parameters structures
typedef union {
foo_param_t foo;
bar_param_t bar;
...
} my_worker_param_t;
// Declare the worker and the task that will execute it
MICROEJ_ASYNC_WORKER_worker_declare(my_worker, MY_WORKER_JOB_COUNT, my_worker_param_t, MY_WORKER_WAITING_LIST_SIZE);
OSAL_task_stack_declare(my_worker_stack, MY_WORKER_STACK_SIZE);
void initialize(){
MICROEJ_ASYNC_WORKER_status_t status = MICROEJ_ASYNC_WORKER_initialize(&my_worker, "My Worker", my_worker_stack, MY_WORKER_PRIORITY);
if(status != MICROEJ_ASYNC_WORKER_OK){
...
}
...
}
int foo(int i, int j){
MICROEJ_ASYNC_WORKER_job_t* job = MICROEJ_ASYNC_WORKER_allocate_job(&my_worker, (SNI_callback)foo);
if(job != NULL){
foo_param_t* params = (foo_param_t*)job->params;
params->i = i;
params->j = j;
MICROEJ_ASYNC_WORKER_status_t status = MICROEJ_ASYNC_WORKER_async_exec(&my_worker, job, foo_action, (SNI_callback)foo_on_done);
if(status != MICROEJ_ASYNC_WORKER_OK){
// Error
}
}
// If an error occurred, then an exception is pending,
// otherwise the current thread is suspended.
// In any case, the returned value is not used.
return -1;
}
void foo_action(MICROEJ_ASYNC_WORKER_job_t* job){
foo_param_t* params = (foo_param_t*)job->params;
int i = params->i;
int j = params->j;
...
params->result = result;
}
int foo_on_done(int i, int j){
foo_param_t* params = (foo_param_t*)job->params;
int result = params->result;
return result;
}
...
Author
MicroEJ Developer Team
Version
0.2.1
Date
13 November 2020

Definition in file microej_async_worker.h.

Macro Definition Documentation

§ MICROEJ_ASYNC_WORKER_worker_declare

#define MICROEJ_ASYNC_WORKER_worker_declare (   _name,
  _job_count,
  _param_type,
  _waiting_list_size 
)
Value:
_param_type _name ## _params[_job_count];\
MICROEJ_ASYNC_WORKER_job_t _name ## _jobs[_job_count];\
int32_t _name ## _waiting_threads[_waiting_list_size+1];\
MICROEJ_ASYNC_WORKER_handle_t _name = {\
.job_count = _job_count,\
.free_jobs = _name ## _jobs,\
.params = _name ## _params,\
.params_sizeof = sizeof(_param_type),\
.waiting_threads_length = _waiting_list_size+1,\
.waiting_threads = _name ## _waiting_threads,\
.waiting_thread_offset = 0,\
.free_waiting_thread_offset = 0\
}

Declares a worker named _name.

This macro must be used outside of any function so the worker is declared as a global variable.

Parameters
_namename of the worker variable.
_job_countmaximum number of jobs that can be allocated for this worker. Must be greater than 0.
_param_typetype of the union of all the parameters structures
_waiting_list_sizeMaximum Java thread that can be suspended on MICROEJ_ASYNC_WORKER_allocate_job() when no job is available. Must be greater than 0.

Definition at line 194 of file microej_async_worker.h.

Function Documentation

§ MICROEJ_ASYNC_WORKER_allocate_job()

MICROEJ_ASYNC_WORKER_job_t* MICROEJ_ASYNC_WORKER_allocate_job ( MICROEJ_ASYNC_WORKER_handle_t async_worker,
SNI_callback  sni_retry_callback 
)

Allocates a new job for the given worker.

If a job is available, returns a reference to the job.

If there is no job available, the current Java thread is suspended, added to the waiting list and NULL is returned. Then, when a job is available, the Java thread is resumed and the function sni_retry_callback is called. Usually the sni_retry_callback function argument is the current SNI function itself (i.e. the function that is currently calling MICROEJ_ASYNC_WORKER_allocate_job()).

If the waiting list is full, an SNI exception is thrown using SNI_throwNativeIOException() and NULL is returned. Size of the waiting list is defined when declaring the worker.

params field of the returned job is a pointer to a type T where T is the value of the argument _param_type given to the MICROEJ_ASYNC_WORKER_worker_declare() macro.

This function must be called within the virtual machine task.

Parameters
[in]async_workerthe worker in which to allocate a job.
[in]sni_retry_callbackif the current Java thread has been suspended, this function is called when it is resumed.
Returns
A job on success or NULL if no job is available.

Definition at line 76 of file microej_async_worker.c.

76  {
77 
78  MICROEJ_ASYNC_WORKER_job_t* job = NULL;
79 
80  OSAL_mutex_take(&async_worker->mutex, OSAL_INFINITE_TIME);
81  {
82  job = async_worker->free_jobs;
83  if(job != NULL){
84  // Free job found: remove it from the free list
85  async_worker->free_jobs = job->_intern.next_free_job;
86  job->_intern.next_free_job = NULL;
87  }
88  }
89  OSAL_mutex_give(&async_worker->mutex);
90 
91  if(job == NULL){
92  // No free job available: wait for a free job.
93  // Store the current thread id in the waiting list.
94  // First check if there is a free element in the waiting list.
95  int32_t free_waiting_thread_offset = async_worker->free_waiting_thread_offset;
96  int32_t new_free_waiting_thread_offset = free_waiting_thread_offset + 1;
97  if(new_free_waiting_thread_offset >= async_worker->waiting_threads_length){
98  new_free_waiting_thread_offset = 0;
99  }
100 
101  if(new_free_waiting_thread_offset == async_worker->waiting_thread_offset){
102  // The waiting list is full.
103  SNI_throwNativeIOException(-1, "MICROEJ_ASYNC_WORKER: thread cannot be suspended, waiting list is full.");
104  }
105  else {
106  async_worker->free_waiting_thread_offset = (uint16_t)new_free_waiting_thread_offset;
107  int32_t thread_id = SNI_getCurrentJavaThreadID();
108  async_worker->waiting_threads[free_waiting_thread_offset] = thread_id;
109  SNI_suspendCurrentJavaThreadWithCallback(0, (SNI_callback) sni_retry_callback, NULL);
110  }
111  }
112 
113  return job;
114 }
A job to execute in a worker.
struct MICROEJ_ASYNC_WORKER_job::@0 _intern
Structure internal data. Must not be modified.

§ MICROEJ_ASYNC_WORKER_async_exec()

MICROEJ_ASYNC_WORKER_status_t MICROEJ_ASYNC_WORKER_async_exec ( MICROEJ_ASYNC_WORKER_handle_t async_worker,
MICROEJ_ASYNC_WORKER_job_t job,
MICROEJ_ASYNC_WORKER_action_t  action,
SNI_callback  on_done_callback 
)

Executes the given job asynchronously.

This function does not block and returns immediately but it suspends the execution of the current Java thread until the job is finished.

When the job is finished, the SNI callback on_done_callback is called before going back to Java. If the job is not used anymore, the callback must released it explicitly by calling MICROEJ_ASYNC_WORKER_free_job().

If an error happens, an SNI exception is thrown using SNI_throwNativeIOException() and the error status MICROEJ_ASYNC_WORKER_ERROR is returned. In this case, the SNI callback on_done_callback is not called and the job must be released explicitly by calling MICROEJ_ASYNC_WORKER_free_job().

This function must be called within the virtual machine task.

Parameters
[in]async_workerthe worker used to execute the given job. Must be the same than the one used to allocate the job.
[in]jobthe job to execute. Must have been allocated with MICROEJ_ASYNC_WORKER_allocate_job().
[in]actionthe function to execute asynchronously.
[in]on_done_callbackthe SNI_callback called when the job is done.
Returns
MICROEJ_ASYNC_WORKER_OK on success, otherwise returns the error status MICROEJ_ASYNC_WORKER_ERROR.

Definition at line 140 of file microej_async_worker.c.

140  {
141  return MICROEJ_ASYNC_WORKER_async_exec_intern(async_worker, job, action, on_done_callback, true);
142 }

§ MICROEJ_ASYNC_WORKER_async_exec_no_wait()

MICROEJ_ASYNC_WORKER_status_t MICROEJ_ASYNC_WORKER_async_exec_no_wait ( MICROEJ_ASYNC_WORKER_handle_t async_worker,
MICROEJ_ASYNC_WORKER_job_t job,
MICROEJ_ASYNC_WORKER_action_t  action 
)

Executes the given job asynchronously.

This function does not block and returns immediately.

When the job is finished, the job is automatically released by the async worker thread.

If an error happens, an SNI exception is thrown using SNI_throwNativeIOException() and the error status MICROEJ_ASYNC_WORKER_ERROR is returned. In this case, the job must be released explicitly by calling MICROEJ_ASYNC_WORKER_free_job().

This function must be called within the virtual machine task.

Parameters
[in]async_workerthe worker used to execute the given job. Must be the same than the one used to allocate the job.
[in]jobthe job to execute. Must have been allocated with MICROEJ_ASYNC_WORKER_allocate_job().
[in]actionthe function to execute asynchronously.
Returns
MICROEJ_ASYNC_WORKER_OK on success, otherwise returns the error status MICROEJ_ASYNC_WORKER_ERROR.

Definition at line 144 of file microej_async_worker.c.

144  {
145  return MICROEJ_ASYNC_WORKER_async_exec_intern(async_worker, job, action, NULL, false);
146 }

§ MICROEJ_ASYNC_WORKER_free_job()

MICROEJ_ASYNC_WORKER_status_t MICROEJ_ASYNC_WORKER_free_job ( MICROEJ_ASYNC_WORKER_handle_t async_worker,
MICROEJ_ASYNC_WORKER_job_t job 
)

Frees a job previously allocated with MICROEJ_ASYNC_WORKER_allocate_job().

This function must be called when and only when the given job has been executed and the result retrieved. This is usually done in the on_done_callback given to MICROEJ_ASYNC_WORKER_async_exec(). After calling this function the given job and its parameters must not be used anymore.

This function must be called within the virtual machine task.

Parameters
[in]async_workerthe worker used to allocate the given job.
[in]jobthe job to free. Must have been allocated with MICROEJ_ASYNC_WORKER_allocate_job().
Returns
MICROEJ_ASYNC_WORKER_OK on success, otherwise returns the error status MICROEJ_ASYNC_WORKER_ERROR.

Definition at line 117 of file microej_async_worker.c.

117  {
118  OSAL_mutex_take(&async_worker->mutex, OSAL_INFINITE_TIME);
119  {
120  job->_intern.next_free_job = async_worker->free_jobs;
121  async_worker->free_jobs = job;
122 
123  int32_t waiting_thread_offset = async_worker->waiting_thread_offset;
124  if(waiting_thread_offset != async_worker->free_waiting_thread_offset){
125  // A thread was waiting for a free job: notify it
126  int32_t thread_id = async_worker->waiting_threads[waiting_thread_offset];
127  int32_t new_waiting_thread_offset = waiting_thread_offset + 1;
128  if(new_waiting_thread_offset >= async_worker->waiting_threads_length){
129  new_waiting_thread_offset = 0;
130  }
131  async_worker->waiting_thread_offset = new_waiting_thread_offset;
132  SNI_resumeJavaThread(thread_id);
133  }
134  }
135  OSAL_mutex_give(&async_worker->mutex);
136 
137  return MICROEJ_ASYNC_WORKER_OK;
138 }
struct MICROEJ_ASYNC_WORKER_job::@0 _intern
Structure internal data. Must not be modified.

§ MICROEJ_ASYNC_WORKER_get_job_done()

MICROEJ_ASYNC_WORKER_job_t* MICROEJ_ASYNC_WORKER_get_job_done ( void  )

Returns the job that has been executed.

This function must be called after the execution of a job in the function passed as on_done_callback argument to MICROEJ_ASYNC_WORKER_async_exec().

Returns
the job that has been executed asynchronously or NULL if not called from an on_done_callback.

Definition at line 172 of file microej_async_worker.c.

172  {
173  MICROEJ_ASYNC_WORKER_job_t* job = NULL;
174  SNI_getCallbackArgs((void**)&job, NULL);
175  return job;
176 }
A job to execute in a worker.

§ MICROEJ_ASYNC_WORKER_initialize()

MICROEJ_ASYNC_WORKER_status_t MICROEJ_ASYNC_WORKER_initialize ( MICROEJ_ASYNC_WORKER_handle_t async_worker,
uint8_t *  name,
OSAL_task_stack_t  stack,
int32_t  priority 
)

Initializes and starts a worker previously declared with MICROEJ_ASYNC_WORKER_worker_declare() macro.

Parameters
[in]async_workerthe worker to initialize. Declared with MICROEJ_ASYNC_WORKER_worker_declare() macro.
[in]nameworker name.
[in]stackworker task stack declared using OSAL_task_stack_declare() macro.
[in]priorityworker task priority.
Returns
MICROEJ_ASYNC_WORKER_INVALID_ARGS if given worker has not been correctly declared. Returns MICROEJ_ASYNC_WORKER_ERROR if worker task or queue creation fails. Returns MICROEJ_ASYNC_WORKER_OK on success.

Definition at line 33 of file microej_async_worker.c.

33  {
34  // Check configuration
35  int32_t job_count = async_worker->job_count;
36  if(job_count <= 0
37  || async_worker->waiting_threads_length <= 1 // compare with 1 because '+1' is added when declaring the array
38  ){
39  return MICROEJ_ASYNC_WORKER_INVALID_ARGS;
40  }
41 
42  // Init jobs
43  MICROEJ_ASYNC_WORKER_job_t* jobs = async_worker->free_jobs;
44  void* params = async_worker->params;
45  int32_t params_sizeof = async_worker->params_sizeof;
46  for(int i=0 ; i<job_count-1 ; i++){
47  jobs[i]._intern.next_free_job = &jobs[i+1];
48  jobs[i].params = params;
49  params = ( void *) ( (int32_t)params + params_sizeof );
50  }
51  jobs[job_count-1]._intern.next_free_job = NULL;
52  jobs[job_count-1].params = params;
53 
54  // Create queue
55  OSAL_status_t res = OSAL_queue_create(name, async_worker->job_count, &async_worker->jobs_queue);
56  if(res != OSAL_OK){
57  return MICROEJ_ASYNC_WORKER_ERROR;
58  }
59 
60  // Create mutex
61  res = OSAL_mutex_create(name, &async_worker->mutex);
62  if(res != OSAL_OK){
63  return MICROEJ_ASYNC_WORKER_ERROR;
64  }
65 
66  // Create task
67  res = OSAL_task_create(MICROEJ_ASYNC_WORKER_loop, name, stack, priority, async_worker, &async_worker->task);
68  if(res != OSAL_OK){
69  return MICROEJ_ASYNC_WORKER_ERROR;
70  }
71 
72  return MICROEJ_ASYNC_WORKER_OK;
73 }
void * params
Pointers to the parameters.
A job to execute in a worker.
struct MICROEJ_ASYNC_WORKER_job::@0 _intern
Structure internal data. Must not be modified.