ASYNC_start_job.pod 11.3 KB
Newer Older
M
Matt Caswell 已提交
1 2 3 4
=pod

=head1 NAME

M
Matt Caswell 已提交
5 6
ASYNC_init_thread, ASYNC_cleanup_thread, ASYNC_start_job, ASYNC_pause_job,
ASYNC_in_job, ASYNC_get_wait_fd, ASYNC_set_wait_fd, ASYNC_clear_wait_fd,
7 8
ASYNC_get_current_job, ASYNC_block_pause, ASYNC_unblock_pause, ASYNC_is_capable
- asynchronous job management functions
M
Matt Caswell 已提交
9 10 11 12 13

=head1 SYNOPSIS

 #include <openssl/async.h>

14 15
 int ASYNC_init_thread(size_t max_size, size_t init_size);
 void ASYNC_cleanup_thread(void);
M
Matt Caswell 已提交
16

M
Matt Caswell 已提交
17 18
 int ASYNC_start_job(ASYNC_JOB **job, ASYNC_WAIT_CTX *ctx, int *ret,
                     int (*func)(void *), void *args, size_t size);
M
Matt Caswell 已提交
19 20 21
 int ASYNC_pause_job(void);

 ASYNC_JOB *ASYNC_get_current_job(void);
M
Matt Caswell 已提交
22
 ASYNC_WAIT_CTX *ASYNC_get_wait_ctx(ASYNC_JOB *job);
23 24
 void ASYNC_block_pause(void);
 void ASYNC_unblock_pause(void);
M
Matt Caswell 已提交
25

26 27
 int ASYNC_is_capable(void);

M
Matt Caswell 已提交
28 29 30 31 32 33 34 35 36 37
=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
M
Matt Caswell 已提交
38 39 40 41 42 43 44 45
pool, used, and then returned to the pool when the job completes. If the user
application is multi-threaded, then ASYNC_init_thread() may be called for each
thread that will initiate asynchronous jobs. Before
user code exits per-thread resources need to be cleaned up. This will normally
occur automatically (see L<OPENSSL_init_crypto(3)>) but may be explicitly
initiated by 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.
46 47 48 49 50 51

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
52 53 54
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
M
Matt Caswell 已提交
55
created up front).
M
Matt Caswell 已提交
56 57

An asynchronous job is started by calling the ASYNC_start_job() function.
M
Matt Caswell 已提交
58 59 60 61 62 63 64
Initially B<*job> should be NULL. B<ctx> should point to an ASYNC_WAIT_CTX
object created through the L<ASYNC_WAIT_CTX_new(3)> function. 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:
M
Matt Caswell 已提交
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84

=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.
85 86
When restarting a job ASYNC_start_job() B<must> be called from the same thread
that the job was originally started from.
M
Matt Caswell 已提交
87 88 89 90 91 92 93 94

=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

95 96 97 98
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.
M
Matt Caswell 已提交
99 100 101 102 103 104 105 106 107 108

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.

M
Matt Caswell 已提交
109 110 111 112 113
ASYNC_get_wait_ctx() can be used to get a pointer to the ASYNC_WAIT_CTX
for the B<job>. ASYNC_WAIT_CTXs can have a "wait" file descriptor associated
with them. 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). If no file descriptor is made
F
FdaSilvaYY 已提交
114
available then an application will have to periodically "poll" the job by
M
Matt Caswell 已提交
115
attempting to restart it to see if it is ready to continue.
M
Matt Caswell 已提交
116 117 118

An example of typical usage might be an async capable engine. User code would
initiate cryptographic operations. The engine would initiate those operations
M
Matt Caswell 已提交
119 120 121 122 123 124 125
asynchronously and then call L<ASYNC_WAIT_CTX_set_wait_fd(3)> followed by
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 by making the wait file descriptor
"readable". Once resumed the engine should clear the wake signal on the wait
file descriptor.
M
Matt Caswell 已提交
126

127 128 129 130 131 132 133
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
F
FdaSilvaYY 已提交
134
application acquires a lock. It then calls some cryptographic function which
135
invokes ASYNC_pause_job(). This returns control back to the code that created
F
FdaSilvaYY 已提交
136
the ASYNC_JOB. If that code then attempts to acquire the same lock before
137
resuming the original job then a deadlock can occur. By calling
A
Alex Gaynor 已提交
138
ASYNC_block_pause() immediately after acquiring the lock and
139 140
ASYNC_unblock_pause() immediately before releasing it then this situation cannot
occur.
M
Matt Caswell 已提交
141

142 143 144
Some platforms cannot support async operations. The ASYNC_is_capable() function
can be used to detect whether the current platform is async capable or not.

M
Matt Caswell 已提交
145 146
=head1 RETURN VALUES

M
Matt Caswell 已提交
147
ASYNC_init_thread returns 1 on success or 0 otherwise.
M
Matt Caswell 已提交
148 149 150 151

ASYNC_start_job returns one of ASYNC_ERR, ASYNC_NO_JOBS, ASYNC_PAUSE or
ASYNC_FINISH as described above.

F
FdaSilvaYY 已提交
152
ASYNC_pause_job returns 0 if an error occurred or 1 on success. If called when
153 154
not within the context of an ASYNC_JOB then this is counted as success so 1 is
returned.
M
Matt Caswell 已提交
155 156 157 158

ASYNC_get_current_job returns a pointer to the currently executing ASYNC_JOB or
NULL if not within the context of a job.

M
Matt Caswell 已提交
159 160
ASYNC_get_wait_ctx() returns a pointer to the ASYNC_WAIT_CTX for the job.

161 162 163
ASYNC_is_capable() returns 1 if the current platform is async capable or 0
otherwise.

M
Matt Caswell 已提交
164 165 166 167 168
=head1 EXAMPLE

The following example demonstrates how to use most of the core async APIs:

 #include <stdio.h>
M
Matt Caswell 已提交
169
 #include <unistd.h>
M
Matt Caswell 已提交
170
 #include <openssl/async.h>
M
Matt Caswell 已提交
171 172 173 174 175 176 177 178 179 180 181 182 183
 #include <openssl/crypto.h>

 #define WAIT_SIGNAL_CHAR   'X'

 int unique = 0;

 void cleanup(ASYNC_WAIT_CTX *ctx, const void *key, OSSL_ASYNC_FD r, void *vw)
 {
     OSSL_ASYNC_FD *w = (OSSL_ASYNC_FD *)vw;
     close(r);
     close(*w);
     OPENSSL_free(w);
 }
M
Matt Caswell 已提交
184 185 186 187 188

 int jobfunc(void *arg)
 {
     ASYNC_JOB *currjob;
     unsigned char *msg;
M
Matt Caswell 已提交
189 190 191
     int pipefds[2] = {0, 0};
     OSSL_ASYNC_FD *wptr;
     char buf = WAIT_SIGNAL_CHAR;
M
Matt Caswell 已提交
192 193 194 195 196 197 198 199 200 201 202 203

     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);

M
Matt Caswell 已提交
204 205 206 207 208 209 210 211 212 213 214 215 216
     if (pipe(pipefds) != 0) {
         printf("Failed to create pipe\n");
         return 0;
     }
     wptr = OPENSSL_malloc(sizeof(OSSL_ASYNC_FD));
     if (wptr == NULL) {
         printf("Failed to malloc\n");
         return 0;
     }
     *wptr = pipefds[1];
     ASYNC_WAIT_CTX_set_wait_fd(ASYNC_get_wait_ctx(currjob), &unique,
                                pipefds[0], wptr, cleanup);

M
Matt Caswell 已提交
217 218 219 220 221 222
     /*
      * 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().
      */
M
Matt Caswell 已提交
223
     write(pipefds[1], &buf, 1);
M
Matt Caswell 已提交
224 225 226 227 228

     /* Return control back to main */
     ASYNC_pause_job();

     /* Clear the wake signal */
M
Matt Caswell 已提交
229
     read(pipefds[0], &buf, 1);
M
Matt Caswell 已提交
230 231 232 233 234 235 236 237 238

     printf ("Resumed the job after a pause\n");

     return 1;
 }

 int main(void)
 {
     ASYNC_JOB *job = NULL;
M
Matt Caswell 已提交
239 240 241
     ASYNC_WAIT_CTX *ctx = NULL;
     int ret;
     OSSL_ASYNC_FD waitfd;
M
Matt Caswell 已提交
242
     fd_set waitfdset;
M
Matt Caswell 已提交
243
     size_t numfds;
M
Matt Caswell 已提交
244 245 246 247
     unsigned char msg[13] = "Hello world!";

     printf("Starting...\n");

M
Matt Caswell 已提交
248 249 250 251 252 253
     ctx = ASYNC_WAIT_CTX_new();
     if (ctx == NULL) {
         printf("Failed to create ASYNC_WAIT_CTX\n");
         abort();
     }

M
Matt Caswell 已提交
254
     for (;;) {
M
Matt Caswell 已提交
255
         switch(ASYNC_start_job(&job, ctx, &ret, jobfunc, msg, sizeof(msg))) {
M
Matt Caswell 已提交
256 257 258 259 260 261 262 263 264 265 266 267 268 269
         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");
M
Matt Caswell 已提交
270 271 272 273 274 275 276
        
         if (!ASYNC_WAIT_CTX_get_all_fds(ctx, NULL, &numfds)
                 || numfds > 1) {
             printf("Unexpected number of fds\n");
             abort();
         }
         ASYNC_WAIT_CTX_get_all_fds(ctx, &waitfd, &numfds);
M
Matt Caswell 已提交
277 278 279 280 281 282
         FD_ZERO(&waitfdset);
         FD_SET(waitfd, &waitfdset);
         select(waitfd + 1, &waitfdset, NULL, NULL, NULL);
     }

 end:
M
Matt Caswell 已提交
283
     ASYNC_WAIT_CTX_free(ctx);
M
Matt Caswell 已提交
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
     printf("Finishing\n");

     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

306 307 308 309
ASYNC_init_thread, ASYNC_cleanup_thread,
ASYNC_start_job, ASYNC_pause_job, ASYNC_get_current_job, ASYNC_get_wait_ctx(),
ASYNC_block_pause(), ASYNC_unblock_pause() and ASYNC_is_capable() were first
added to OpenSSL 1.1.0.
M
Matt Caswell 已提交
310 311

=cut