68487a9b06
In theory the pthreads approach for Thread Local Storage should be more portable. This also changes some APIs in order to accommodate this change. In particular ASYNC_init_pool is renamed ASYNC_init_thread and ASYNC_free_pool is renamed ASYNC_cleanup_thread. Also introduced ASYNC_init and ASYNC_cleanup. Reviewed-by: Rich Salz <rsalz@openssl.org>
277 lines
11 KiB
Text
277 lines
11 KiB
Text
=pod
|
|
|
|
=head1 NAME
|
|
|
|
ASYNC_init, ASYNC_cleanup, ASYNC_init_thread, ASYNC_cleanup_thread,
|
|
ASYNC_start_job, ASYNC_pause_job, ASYNC_in_job, ASYNC_get_wait_fd,
|
|
ASYNC_get_current_job, ASYNC_wake, ASYNC_clear_wake, ASYNC_block_pause,
|
|
ASYNC_unblock_pause - asynchronous job management functions
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
#include <openssl/async.h>
|
|
|
|
int ASYNC_init(int init_thread, size_t max_size, size_t init_size);
|
|
void ASYNC_cleanup(int cleanupthread);
|
|
|
|
int ASYNC_init_thread(size_t max_size, size_t init_size);
|
|
void ASYNC_cleanup_thread(void);
|
|
|
|
int ASYNC_start_job(ASYNC_JOB **job, int *ret, int (*func)(void *),
|
|
void *args, size_t size);
|
|
int ASYNC_pause_job(void);
|
|
|
|
int ASYNC_get_wait_fd(ASYNC_JOB *job);
|
|
ASYNC_JOB *ASYNC_get_current_job(void);
|
|
void ASYNC_wake(ASYNC_JOB *job);
|
|
void ASYNC_clear_wake(ASYNC_JOB *job);
|
|
void ASYNC_block_pause(void);
|
|
void ASYNC_unblock_pause(void);
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
OpenSSL implements asynchronous capabilities through an ASYNC_JOB. This
|
|
represents code that can be started and executes until some event occurs. At
|
|
that point the code can be paused and control returns to user code until some
|
|
subsequent event indicates that the job can be resumed.
|
|
|
|
The creation of an ASYNC_JOB is a relatively expensive operation. Therefore, for
|
|
efficiency reasons, jobs can be created up front and reused many times. They are
|
|
held in a pool until they are needed, at which point they are removed from the
|
|
pool, used, and then returned to the pool when the job completes. Before using
|
|
any of the asynchronous job functions, user code should first call
|
|
ASYNC_init(). If the user application is multi-threaded, then
|
|
ASYNC_init_thread() should be called for each thread that will initiate
|
|
asynchronous jobs. If the B<init_thread> parameter to ASYNC_init() is non-zero
|
|
then ASYNC_init_thread is automatically called for the current thread. Before
|
|
user code exits it should free up resources for each thread that was initialised
|
|
using ASYNC_cleanup_thread(). No asynchronous jobs must be outstanding for the thread
|
|
when ASYNC_cleanup_thread() is called. Failing to ensure this will result in memory
|
|
leaks. Additionally an application should call ASYNC_cleanup() when all
|
|
asynchronous work is complete across all threads. If B<cleanupthread> is
|
|
non-zero then ASYNC_cleanup_thread() is automatically called for the current
|
|
thread.
|
|
|
|
The B<max_size> argument limits the number of ASYNC_JOBs that will be held in
|
|
the pool. If B<max_size> is set to 0 then no upper limit is set. When an
|
|
ASYNC_JOB is needed but there are none available in the pool already then one
|
|
will be automatically created, as long as the total of ASYNC_JOBs managed by the
|
|
pool does not exceed B<max_size>. When the pool is first initialised
|
|
B<init_size> ASYNC_JOBs will be created immediately. If ASYNC_init_thread() is
|
|
not called before the pool is first used then it will be called automatically
|
|
with a B<max_size> of 0 (no upper limit) and an B<init_size> of 0 (no ASYNC_JOBs
|
|
created up front). If a pool is created in this way it must still be cleaned up
|
|
with an explicit call to ASYNC_cleanup_thread().
|
|
|
|
An asynchronous job is started by calling the ASYNC_start_job() function.
|
|
Initially B<*job> should be NULL. B<ret> should point to a location where the
|
|
return value of the asynchronous function should be stored on completion of the
|
|
job. B<func> represents the function that should be started asynchronously. The
|
|
data pointed to by B<args> and of size B<size> will be copied and then passed as
|
|
an argument to B<func> when the job starts. ASYNC_start_job will return one of
|
|
the following values:
|
|
|
|
=over 4
|
|
|
|
=item B<ASYNC_ERR>
|
|
|
|
An error occurred trying to start the job. Check the OpenSSL error queue (e.g.
|
|
see L<ERR_print_errors(3)>) for more details.
|
|
|
|
=item B<ASYNC_NO_JOBS>
|
|
|
|
There are no jobs currently available in the pool. This call can be retried
|
|
again at a later time.
|
|
|
|
=item B<ASYNC_PAUSE>
|
|
|
|
The job was successfully started but was "paused" before it completed (see
|
|
ASYNC_pause_job() below). A handle to the job is placed in B<*job>. Other work
|
|
can be performed (if desired) and the job restarted at a later time. To restart
|
|
a job call ASYNC_start_job() again passing the job handle in B<*job>. The
|
|
B<func>, B<args> and B<size> parameters will be ignored when restarting a job.
|
|
When restarting a job ASYNC_start_job() B<must> be called from the same thread
|
|
that the job was originally started from.
|
|
|
|
=item B<ASYNC_FINISH>
|
|
|
|
The job completed. B<*job> will be NULL and the return value from B<func> will
|
|
be placed in B<*ret>.
|
|
|
|
=back
|
|
|
|
At any one time there can be a maximum of one job actively running per thread
|
|
(you can have many that are paused). ASYNC_get_current_job() can be used to get
|
|
a pointer to the currently executing ASYNC_JOB. If no job is currently executing
|
|
then this will return NULL.
|
|
|
|
If executing within the context of a job (i.e. having been called directly or
|
|
indirectly by the function "func" passed as an argument to ASYNC_start_job())
|
|
then ASYNC_pause_job() will immediately return control to the calling
|
|
application with ASYNC_PAUSE returned from the ASYNC_start_job() call. A
|
|
subsequent call to ASYNC_start_job passing in the relevant ASYNC_JOB in the
|
|
B<*job> parameter will resume execution from the ASYNC_pause_job() call. If
|
|
ASYNC_pause_job() is called whilst not within the context of a job then no
|
|
action is taken and ASYNC_pause_job() returns immediately.
|
|
|
|
Every ASYNC_JOB has a "wait" file descriptor associated with it. Calling
|
|
ASYNC_get_wait_fd() and passing in a pointer to an ASYNC_JOB in the B<job>
|
|
parameter will return the wait file descriptor associated with that job. This
|
|
file descriptor can be used to signal that the job should be resumed.
|
|
Applications can wait for the file descriptor to be ready for "read" using a
|
|
system function call such as select or poll (being ready for "read" indicates
|
|
that the job should be resumed). Applications can signal that a job is ready to
|
|
resume using ASYNC_wake() or clear an existing signal using ASYNC_clear_wake().
|
|
|
|
An example of typical usage might be an async capable engine. User code would
|
|
initiate cryptographic operations. The engine would initiate those operations
|
|
asynchronously and then call ASYNC_pause_job() to return control to the user
|
|
code. The user code can then perform other tasks or wait for the job to be ready
|
|
by calling "select" or other similar function on the wait file descriptor. The
|
|
engine can signal to the user code that the job should be resumed using
|
|
ASYNC_wake(). Once resumed the engine would clear the wake signal by calling
|
|
ASYNC_clear_wake().
|
|
|
|
The ASYNC_block_pause() function will prevent the currently active job from
|
|
pausing. The block will remain in place until a subsequent call to
|
|
ASYNC_unblock_pause(). These functions can be nested, e.g. if you call
|
|
ASYNC_block_pause() twice then you must call ASYNC_unblock_pause() twice in
|
|
order to reenable pausing. If these functions are called while there is no
|
|
currently active job then they have no effect. This functionality can be useful
|
|
to avoid deadlock scenarios. For example during the execution of an ASYNC_JOB an
|
|
application aquires a lock. It then calls some cryptographic function which
|
|
invokes ASYNC_pause_job(). This returns control back to the code that created
|
|
the ASYNC_JOB. If that code then attempts to aquire the same lock before
|
|
resuming the original job then a deadlock can occur. By calling
|
|
ASYNC_block_pause() immediately after aquiring the lock and
|
|
ASYNC_unblock_pause() immediately before releasing it then this situation cannot
|
|
occur.
|
|
|
|
=head1 RETURN VALUES
|
|
|
|
ASYNC_init and ASYNC_init_thread return 1 on success or 0 otherwise.
|
|
|
|
ASYNC_start_job returns one of ASYNC_ERR, ASYNC_NO_JOBS, ASYNC_PAUSE or
|
|
ASYNC_FINISH as described above.
|
|
|
|
ASYNC_pause_job returns 0 if an error occured or 1 on success. If called when
|
|
not within the context of an ASYNC_JOB then this is counted as success so 1 is
|
|
returned.
|
|
|
|
ASYNC_get_wait_fd returns the "wait" file descriptor associated with the
|
|
ASYNC_JOB provided as an argument.
|
|
|
|
ASYNC_get_current_job returns a pointer to the currently executing ASYNC_JOB or
|
|
NULL if not within the context of a job.
|
|
|
|
=head1 EXAMPLE
|
|
|
|
The following example demonstrates how to use most of the core async APIs:
|
|
|
|
#include <stdio.h>
|
|
#include <openssl/async.h>
|
|
|
|
int jobfunc(void *arg)
|
|
{
|
|
ASYNC_JOB *currjob;
|
|
unsigned char *msg;
|
|
|
|
currjob = ASYNC_get_current_job();
|
|
if (currjob != NULL) {
|
|
printf("Executing within a job\n");
|
|
} else {
|
|
printf("Not executing within a job - should not happen\n");
|
|
return 0;
|
|
}
|
|
|
|
msg = (unsigned char *)arg;
|
|
printf("Passed in message is: %s\n", msg);
|
|
|
|
/*
|
|
* Normally some external event would cause this to happen at some
|
|
* later point - but we do it here for demo purposes, i.e.
|
|
* immediately signalling that the job is ready to be woken up after
|
|
* we return to main via ASYNC_pause_job().
|
|
*/
|
|
ASYNC_wake(currjob);
|
|
|
|
/* Return control back to main */
|
|
ASYNC_pause_job();
|
|
|
|
/* Clear the wake signal */
|
|
ASYNC_clear_wake(currjob);
|
|
|
|
printf ("Resumed the job after a pause\n");
|
|
|
|
return 1;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
ASYNC_JOB *job = NULL;
|
|
int ret, waitfd;
|
|
fd_set waitfdset;
|
|
unsigned char msg[13] = "Hello world!";
|
|
|
|
/*
|
|
* We're only expecting 1 job to be used here so we're only creating
|
|
* a pool of 1
|
|
*/
|
|
if (!ASYNC_init(1, 1, 1)) {
|
|
printf("Error creating pool\n");
|
|
goto end;
|
|
}
|
|
|
|
printf("Starting...\n");
|
|
|
|
for (;;) {
|
|
switch(ASYNC_start_job(&job, &ret, jobfunc, msg, sizeof(msg))) {
|
|
case ASYNC_ERR:
|
|
case ASYNC_NO_JOBS:
|
|
printf("An error occurred\n");
|
|
goto end;
|
|
case ASYNC_PAUSE:
|
|
printf("Job was paused\n");
|
|
break;
|
|
case ASYNC_FINISH:
|
|
printf("Job finished with return value %d\n", ret);
|
|
goto end;
|
|
}
|
|
|
|
/* Wait for the job to be woken */
|
|
printf("Waiting for the job to be woken up\n");
|
|
waitfd = ASYNC_get_wait_fd(job);
|
|
FD_ZERO(&waitfdset);
|
|
FD_SET(waitfd, &waitfdset);
|
|
select(waitfd + 1, &waitfdset, NULL, NULL, NULL);
|
|
}
|
|
|
|
end:
|
|
printf("Finishing\n");
|
|
ASYNC_cleanup(1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
The expected output from executing the above example program is:
|
|
|
|
Starting...
|
|
Executing within a job
|
|
Passed in message is: Hello world!
|
|
Job was paused
|
|
Waiting for the job to be woken up
|
|
Resumed the job after a pause
|
|
Job finished with return value 1
|
|
Finishing
|
|
|
|
=head1 SEE ALSO
|
|
|
|
L<crypto(3)>, L<ERR_print_errors(3)>
|
|
|
|
=head1 HISTORY
|
|
|
|
ASYNC_init, ASYNC_init_thread, ASYNC_cleanup, ASYNC_cleanup_thread,
|
|
ASYNC_start_job, ASYNC_pause_job, ASYNC_get_wait_fd, ASYNC_get_current_job,
|
|
ASYNC_wake, ASYNC_clear_wake were first added to OpenSSL 1.1.0.
|
|
|
|
=cut
|