File: sftp_aio.dox

package info (click to toggle)
libssh 0.11.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 6,420 kB
  • sloc: ansic: 100,132; cpp: 421; sh: 186; makefile: 25; javascript: 20; python: 9
file content (705 lines) | stat: -rw-r--r-- 24,952 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
/**

@page libssh_tutor_sftp_aio Chapter 10: The SFTP asynchronous I/O

@section sftp_aio_api The SFTP asynchronous I/O

NOTE : Please read @ref libssh_tutor_sftp before reading this page. The
synchronous sftp_read() and sftp_write() have been described there.

SFTP AIO stands for "SFTP Asynchronous Input/Output". This API contains
functions which perform async read/write operations on remote files.

File transfers performed using the asynchronous sftp aio API can be
significantly faster than the file transfers performed using the synchronous
sftp read/write API (see sftp_read() and sftp_write()).

The sftp aio API functions are divided into two categories :
  - sftp_aio_begin_*() [see sftp_aio_begin_read(), sftp_aio_begin_write()]:
    These functions send a request for an i/o operation to the server and
    provide the caller an sftp aio handle corresponding to the sent request.

  - sftp_aio_wait_*() [see sftp_aio_wait_read(), sftp_aio_wait_write()]:
    These functions wait for the server response corresponding to a previously
    issued request. Which request ? the request corresponding to the sftp aio
    handle supplied by the caller to these functions.

Conceptually, you can think of the sftp aio handle as a request identifier.

Technically, the sftp_aio_begin_*() functions dynamically allocate memory to
store information about the i/o request they send and provide the caller a
handle to this memory, we call this handle an sftp aio handle.

sftp_aio_wait_*() functions use the information stored in that memory (handled
by the caller supplied sftp aio handle) to identify a request, and then they
wait for that request's response. These functions also release the memory
handled by the caller supplied sftp aio handle (except when they return
SSH_AGAIN).

sftp_aio_free() can also be used to release the memory handled by an sftp aio
handle but unlike the sftp_aio_wait_*() functions, it doesn't wait for a
response. This should be used to release the memory corresponding to an sftp
aio handle when some failure occurs. An example has been provided at the
end of this page to show the usage of sftp_aio_free().

To begin with, this tutorial will provide basic examples that describe the
usage of sftp aio API to perform a single read/write operation.

The later sections describe the usage of the sftp aio API to obtain faster file
transfers as compared to the transfers performed using the synchronous sftp
read/write API.

On encountering an error, the sftp aio API functions set the sftp and ssh
errors just like any other libssh sftp API function. These errors can be
obtained using sftp_get_error(), ssh_get_error() and ssh_get_error_code().
The code examples provided on this page ignore error handling for the sake of
brevity.

@subsection sftp_aio_read Using the sftp aio API for reading (a basic example)

For performing an async read operation on a sftp file (see sftp_open()),
the first step is to call sftp_aio_begin_read() to send a read request to the
server. The caller is provided an sftp aio handle corresponding to the sent
read request.

The second step is to pass a pointer to this aio handle to
sftp_aio_wait_read(), this function waits for the server response which
indicates the success/failure of the read request. On success, the response
indicates EOF or contains the data read from the sftp file.

The following code example shows how a read operation can be performed
on an sftp file using the sftp aio API.

@code
ssize_t read_chunk(sftp_file file, void *buf, size_t to_read)
{
    ssize_t bytes_requested, bytes_read;

    // Variable to store an sftp aio handle
    sftp_aio aio = NULL;

    // Send a read request to the sftp server
    bytes_requested = sftp_aio_begin_read(file, to_read, &aio);
    if (bytes_requested == SSH_ERROR) {
        // handle error
    }

    // Here its possible that (bytes_requested < to_read) as specified in
    // the function documentation of sftp_aio_begin_read()

    // Wait for the response of the read request corresponding to the
    // sftp aio handle stored in the aio variable.
    bytes_read = sftp_aio_wait_read(&aio, buf, to_read);
    if (bytes_read == SSH_ERROR) {
        // handle error
    }

    return bytes_read;
}
@endcode

@subsection sftp_aio_write Using the sftp aio API for writing (a basic example)

For performing an async write operation on a sftp file (see sftp_open()),
the first step is to call sftp_aio_begin_write() to send a write request to
the server. The caller is provided an sftp aio handle corresponding to the
sent write request.

The second step is to pass a pointer to this aio handle to
sftp_aio_wait_write(), this function waits for the server response which
indicates the success/failure of the write request.

The following code example shows how a write operation can be performed on an
sftp file using the sftp aio API.

@code
ssize_t write_chunk(sftp_file file, void *buf, size_t to_write)
{
    ssize_t bytes_requested, bytes_written;

    // Variable to store an sftp aio handle
    sftp_aio aio = NULL;

    // Send a write request to the sftp server
    bytes_requested = sftp_aio_begin_write(file, buf, to_write, &aio);
    if (bytes_requested == SSH_ERROR) {
        // handle error
    }

    // Here its possible that (bytes_requested < to_write) as specified in
    // the function documentation of sftp_aio_begin_write()

    // Wait for the response of the write request corresponding to
    // the sftp aio handle stored in the aio variable.
    bytes_written = sftp_aio_wait_write(&aio);
    if (bytes_written == SSH_ERROR) {
        // handle error
    }

    return bytes_written;
}
@endcode

@subsection sftp_aio_actual_use Using the sftp aio API to speed up a transfer

The above examples were provided to introduce the sftp aio API.
This is not how the sftp aio API is intended to be used, because the
above usage offers no advantage over the synchronous sftp read/write API
which does the same thing i.e issue a request and then immediately wait for
its response.

The facility that the sftp aio API provides is that the user can do
anything between issuing a request and getting the corresponding response.
Any number of operations can be performed after calling sftp_aio_begin_*()
[which issues a request] and before calling sftp_aio_wait_*() [which waits
for a response]

The code can leverage this feature by calling sftp_aio_begin_*() multiple times
to issue multiple requests before calling sftp_aio_wait_*() to wait for the
response of an earlier issued request. This approach will keep a certain number
of requests outstanding at the client side.

After issuing those requests, while the client code does something else (for
example waiting for an outstanding request's response, processing an obtained
response, issuing another request or any other operation the client wants
to perform), at the same time :

  - Some of those outstanding requests may be travelling over the
    network towards the server.

  - Some of the outstanding requests may have reached the server and may
    be queued for processing at the server side.

  - Some of the outstanding requests may have been processed and the
    corresponding responses may be travelling over the network towards the
    client.

  - Some of the responses corresponding to the outstanding requests may
    have already reached the client side.

Clearly in this case, operations that the client performs and operations
involved in transfer/processing of a outstanding request can occur in
parallel. Also, operations involved in transfer/processing of two or more
outstanding requests may also occur in parallel (for example when one request
travels to the server, another request's response may be incoming towards the
client). Such kind of parallelism makes the overall transfer faster as compared
to a transfer performed using the synchronous sftp read/write API.

When the synchronous sftp read/write API is used to perform a transfer,
a strict sequence is followed:

  - The client issues a single read/write request.
  - Then waits for its response.
  - On obtaining the response, the client processes it.
  - After the processing ends, the client issues the next read/write request.

A file transfer performed in this manner would be slower than the case where
multiple read/write requests are kept outstanding at the client side. Because
here at any given time, operations related to transfer/processing of only one
request/response pair occurs. This is in contrast to the multiple outstanding
requests scenario where operations related to transfer/processing of multiple
request/response pairs may occur at the same time.

Although it's true that keeping multiple requests outstanding can speed up a
transfer, those outstanding requests come at a cost of increased memory
consumption both at the client side and the server side. Hence care must be
taken to use a reasonable limit for the number of requests kept outstanding.

The further sections provide code examples to show how uploads/downloads
can be performed using the sftp aio API and the concept of outstanding requests
discussed in this section. In those code examples, error handling has been
ignored and at some places pseudo code has been used for the sake of brevity.

The complete code for performing uploads/downloads using the sftp aio API,
can be found at https://gitlab.com/libssh/libssh-mirror/-/tree/master.

  - libssh benchmarks for uploads performed using the sftp aio API [See
    tests/benchmarks/bench_sftp.c]
  - libssh benchmarks for downloads performed using the sftp aio API. [See
    tests/benchmarks/bench_sftp.c]
  - libssh sftp ft API code for performing a local to remote transfer (upload).
    [See src/sftp_ft.c]
  - libssh sftp ft API code for performing a remote to local transfer
    (download). [See src/sftp_ft.c]

@subsection sftp_aio_cap Capping applied by the sftp aio API

Before the code examples for uploads and downloads, its important
to know about the capping applied by the sftp aio API.

sftp_aio_begin_read() caps the number of bytes the caller can request
to read from the remote file. That cap is the value of the max_read_length
field of the sftp_limits_t returned by sftp_limits(). Say that cap is LIM
and the caller passes x as the number of bytes to read to
sftp_aio_begin_read(), then (assuming no error occurs) :

  - if x <= LIM, then sftp_aio_begin_read() will request the server
    to read x bytes from the remote file, and will return x.

  - if x > LIM, then sftp_aio_begin_read() will request the server
    to read LIM bytes from the remote file and will return LIM.

Hence to request server to read x bytes (> LIM), the caller would have
to call sftp_aio_begin_read() multiple times, typically in a loop and
break out of the loop when the summation of return values of the multiple
sftp_aio_begin_read() calls becomes equal to x.

For the sake of simplicity, the code example for download in the upcoming
section would always ask sftp_aio_begin_read() to read x <= LIM bytes,
so that its return value is guaranteed to be x, unless an error occurs.

Similarly, sftp_aio_begin_write() caps the number of bytes the caller
can request to write to the remote file. That cap is the value of
max_write_length field of the sftp_limits_t returned by sftp_limits().
Say that cap is LIM and the caller passes x as the number of bytes to
write to sftp_aio_begin_write(), then (assuming no error occurs) :

  - if x <= LIM, then sftp_aio_begin_write() will request the server
    to write x bytes to the remote file, and will return x.

  - if x > LIM, then sftp_aio_begin_write() will request the server
    to write LIM bytes to the remote file and will return LIM.

Hence to request server to write x bytes (> LIM), the caller would have
to call sftp_aio_begin_write() multiple times, typically in a loop and
break out of the loop when the summation of return values of the multiple
sftp_aio_begin_write() calls becomes equal to x.

For the sake of simplicity, the code example for upload in the upcoming
section would always ask sftp_aio_begin_write() to write x <= LIM bytes,
so that its return value is guaranteed to be x, unless an error occurs.

@subsection sftp_aio_download_example Performing a download using the sftp aio API

Terminologies used in the following code snippets :

  - sftp : The sftp_session opened using sftp_new() and initialised using
    sftp_init()

  - file : The sftp file handle of the remote file to download data
    from. (See sftp_open())

  - file_size : the size of the sftp file to download. This size can be obtained
    by statting the remote file to download (e.g by using sftp_stat())

  - We will need to maintain a queue which will be used to store the sftp aio
    handles corresponding to the outstanding requests.

First, we issue the read requests while ensuring that their count
doesn't exceed a particular limit decided by us, and the number of bytes
requested don't exceed the size of the file to download.

@code
sftp_aio aio = NULL;

// Chunk size to use for the transfer
size_t chunk_size;

// For the limits structure that would be used
// by the code to set the chunk size
sftp_limits_t lim = NULL;

// Max number of requests to keep outstanding at a time
size_t in_flight_requests = 5;

// Number of bytes for which requests have been sent
size_t total_bytes_requested = 0;

// Number of bytes which have been downloaded
size_t bytes_downloaded = 0;

// Buffer to use for the download
char *buffer = NULL;

// Helper variables
size_t to_read;
ssize_t bytes_requested;

// Get the sftp limits
lim = sftp_limits(sftp);
if (lim == NULL) {
    // handle error
}

// Set the chunk size for download = the max limit for reading
// The reason for this has been given in the "Capping applied by
// the sftp aio API" section (Its to make the code simpler)
//
// Assigning a size_t type variable a uint64_t type value here,
// theoretically could cause an overflow, but practically
// max_read_length would never exceed SIZE_MAX so its okay.
chunk_size = lim->max_read_length;

buffer = malloc(chunk_size);
if (buffer == NULL) {
    // handle error
}

... // Code to open the remote file (to download) using sftp_open().
... // Code to stat the remote file's file size.
... // Code to open the local file in which downloaded data is to be stored.
... // Code to initialize the queue which will be used to store sftp aio
    // handles.

for (i = 0;
     i < in_flight_requests && total_bytes_requested < file_size;
     ++i) {
    to_read = file_size - total_bytes_requested;
    if (to_read > chunk_size) {
        to_read = chunk_size;
    }

    // Issue a read request
    bytes_requested = sftp_aio_begin_read(file, to_read, &aio);
    if (bytes_requested == SSH_ERROR) {
        // handle error
    }

    if ((size_t)bytes_requested < to_read) {
        // Should not happen for this code, as the to_read is <=
        // max limit for reading (chunk size), so there is no reason
        // for sftp_aio_begin_read() to return a lesser value.
    }

    total_bytes_requested += (size_t)bytes_requested;

    // Pseudo code
    ENQUEUE aio in the queue;
}

@endcode

At this point, at max in_flight_requests number of requests may be
outstanding. Now we wait for the response corresponding to the earliest
issued outstanding request.

On getting that response, we issue another read request if there are
still some bytes in the sftp file (to download) for which we haven't sent the
read request. (This happens when total_bytes_requested < file_size)

This issuing of another read request (under a condition) is done to
keep the number of outstanding requests equal to the value of the
in_flight_requests variable.

This process has to be repeated for every remaining outstanding request.

@code
while (the queue is not empty) {
    // Pseudo code
    aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles;

    // Wait for the response of the request corresponding to the aio
    bytes_read = sftp_aio_wait_read(&aio, buffer, chunk_size);
    if (bytes_read == SSH_ERROR) {
        //handle error
    }

    bytes_downloaded += bytes_read;
    if (bytes_read != chunk_size && bytes_downloaded != file_size) {
        // A short read encountered on the remote file before reaching EOF,
        // short read before reaching EOF should never happen for the sftp aio
        // API which respects the max limit for reading. This probably
        // indicates a bad server.
    }

    // Pseudo code
    WRITE bytes_read bytes from the buffer into the local file
    in which downloaded data is to be stored ;

    if (total_bytes_requested == file_size) {
        // no need to issue more read requests
        continue;
    }

    // else issue a read request
    to_read = file_size - total_bytes_requested;
    if (to_read > chunk_size) {
        to_read = chunk_size;
    }

    bytes_requested = sftp_aio_begin_read(file, to_read, &aio);
    if (bytes_requested == SSH_ERROR) {
        // handle error
    }

    if ((size_t)bytes_requested < to_read) {
        // Should not happen for this code, as the to_read is <=
        // max limit for reading (chunk size), so there is no reason
        // for sftp_aio_begin_read() to return a lesser value.
    }

    total_bytes_requested += bytes_requested;

    // Pseudo code
    ENQUEUE aio in the queue;
}

free(buffer);
sftp_limits_free(lim);

... // Code to destroy the queue which was used to store the sftp aio
    // handles.
@endcode

After exiting the while (the queue is not empty) loop, the download
would've been complete (assuming no error occurs).

@subsection sftp_aio_upload_example Performing an upload using the sftp aio API

Terminologies used in the following code snippets :

  - sftp : The sftp_session opened using sftp_new() and initialised using
    sftp_init()

  - file : The sftp file handle of the remote file in which uploaded data
    is to be stored. (See sftp_open())

  - file_size : The size of the local file to upload. This size can be
    obtained by statting the local file to upload (e.g by using stat())

  - We will need maintain a queue which will be used to store the sftp aio
    handles corresponding to the outstanding requests.

First, we issue the write requests while ensuring that their count
doesn't exceed a particular limit decided by us, and the number of bytes
requested to write don't exceed the size of the file to upload.

@code
sftp_aio aio = NULL;

// The chunk size to use for the transfer
size_t chunk_size;

// For the limits structure that would be used by
// the code to set the chunk size
sftp_limits_t lim = NULL;

// Max number of requests to keep outstanding at a time
size_t in_flight_requests = 5;

// Total number of bytes for which write requests have been sent
size_t total_bytes_requested = 0;

// Buffer to use for the upload
char *buffer = NULL;

// Helper variables
size_t to_write;
ssize_t bytes_requested;

// Get the sftp limits
lim = sftp_limits(sftp);
if (lim == NULL) {
    // handle error
}

// Set the chunk size for upload = the max limit for writing.
// The reason for this has been given in the "Capping applied by
// the sftp aio API" section (Its to make the code simpler)
//
// Assigning a size_t type variable a uint64_t type value here,
// theoretically could cause an overflow, but practically
// max_write_length would never exceed SIZE_MAX so its okay.
chunk_size = lim->max_write_length;

buffer = malloc(chunk_size);
if (buffer == NULL) {
    // handle error
}

... // Code to open the local file (to upload) [e.g using open(), fopen()].
... // Code to stat the local file's file size [e.g using stat()].
... // Code to open the remote file in which uploaded data will be stored [see
    // sftp_open()].
... // Code to initialize the queue which will be used to store sftp aio
    // handles.

for (i = 0;
     i < in_flight_requests && total_bytes_requested < file_size;
     ++i) {
    to_write = file_size - total_bytes_requested;
    if (to_write > chunk_size) {
        to_write = chunk_size;
    }

    // Pseudo code
    READ to_write bytes from the local file (to upload) into the buffer;

    bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio);
    if (bytes_requested == SSH_ERROR) {
        // handle error
    }

    if ((size_t)bytes_requested < to_write) {
        // Should not happen for this code, as the to_write is <=
        // max limit for writing (chunk size), so there is no reason
        // for sftp_aio_begin_write() to return a lesser value.
    }

    total_bytes_requested += (size_t)bytes_requested;

    // Pseudo code
    ENQUEUE aio in the queue;
}

@endcode

At this point, at max in_flight_requests number of requests may be
outstanding. Now we wait for the response corresponding to the earliest
issued outstanding request.

On getting that response, we issue another write request if there are
still some bytes in the local file (to upload) for which we haven't sent
the write request. (This happens when total_bytes_requested < file_size)

This issuing of another write request (under a condition) is done to
keep the number of outstanding requests equal to the value of the
in_flight_requests variable.

This process has to be repeated for every remaining outstanding request.

@code
while (the queue is not empty) {
    // Pseudo code
    aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles;

    // Wait for the response of the request corresponding to the aio
    bytes_written = sftp_aio_wait_write(&aio);
    if (bytes_written == SSH_ERROR) {
        // handle error
    }

    // sftp_aio_wait_write() won't report a short write, so no need
    // to check for a short write here.

    if (total_bytes_requested == file_size) {
        // no need to issue more write requests
        continue;
    }

    // else issue a write request
    to_write = file_size - total_bytes_requested;
    if (to_write > chunk_size) {
        to_write = chunk_size;
    }

    // Pseudo code
    READ to_write bytes from the local file (to upload) into a buffer;

    bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio);
    if (bytes_requested == SSH_ERROR) {
        // handle error
    }

    if ((size_t)bytes_requested < to_write) {
        // Should not happen for this code, as the to_write is <=
        // max limit for writing (chunk size), so there is no reason
        // for sftp_aio_begin_write() to return a lesser value.
    }

    total_bytes_requested += (size_t)bytes_requested;

    // Pseudo code
    ENQUEUE aio in the queue;
}

free(buffer);

... // Code to destroy the queue which was used to store the sftp aio
    // handles.
@endcode

After exiting the while (the queue is not empty) loop, the upload
would've been complete (assuming no error occurs).

@subsection sftp_aio_free Example showing the usage of sftp_aio_free()

The purpose of sftp_aio_free() was discussed at the beginning of this page,
the following code example shows how it can be used during cleanup.

@code
void print_sftp_error(sftp_session sftp)
{
    if (sftp == NULL) {
        return;
    }

    fprintf(stderr, "sftp error : %d\n", sftp_get_error(sftp));
    fprintf(stderr, "ssh error : %s\n", ssh_get_error(sftp->session));
}

// Returns 0 on success, -1 on error
int write_strings(sftp_file file)
{
    const char * strings[] = {
        "This is the first string",
        "This is the second string",
        "This is the third string",
        "This is the fourth string"
    };

    size_t string_count = sizeof(strings) / sizeof(strings[0]);
    size_t i;

    sftp_session sftp = NULL;
    sftp_aio aio = NULL;

    int rc;

    if (file == NULL) {
        return -1;
    }

    ... // Code to initialize the queue which will be used to store sftp aio
        // handles

    sftp = file->sftp;
    for (i = 0; i < string_count; ++i) {
        rc = sftp_aio_begin_write(file,
                                  strings[i],
                                  strlen(strings[i]),
                                  &aio);
        if (rc == SSH_ERROR) {
            print_sftp_error(sftp);
            goto err;
        }

        // Pseudo code
        ENQUEUE aio in the queue of sftp aio handles
    }

    for (i = 0; i < string_count; ++i) {
        // Pseudo code
        aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles;

        rc = sftp_aio_wait_write(&aio);
        if (rc == SSH_ERROR) {
            print_sftp_error(sftp);
            goto err;
        }
    }


    ... // Code to destroy the queue in which sftp aio handles were
        // stored

    return 0;

err:

    while (queue is not empty) {
        // Pseudo code
        aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles;

        sftp_aio_free(aio);
    }

    ... // Code to destroy the queue in which sftp aio handles were
        // stored.

    return -1;
}

@endcode

*/