<div dir="ltr"><div dir="ltr">On Mon, Mar 8, 2021 at 12:59 AM Nir Soffer <<a href="mailto:nsoffer@redhat.com">nsoffer@redhat.com</a>> wrote:<br></div><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div dir="ltr">On Mon, Mar 8, 2021 at 12:25 AM Richard W.M. Jones <<a href="mailto:rjones@redhat.com" target="_blank">rjones@redhat.com</a>> wrote:<br></div><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">On Sat, Mar 06, 2021 at 11:21:22PM +0200, Nir Soffer wrote:<br>
> Handling extents is complicated, in particular when using async block<br>
> status. But I could not get this working using sync block status; It<br>
> seems that libnbd eats the events on the source, and then we hang<br>
> forever waiting for inflight reads.<br>
<br>
Is this a bug in libnbd?<br></blockquote><div><br></div><div>Maybe, I can try to post a simple reproducer later. Even if this is not a bug</div><div>it would be nice to show how sync calls can be mixed with async call with</div><div>libev (or another event loop).</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
Also is libev going to be faster than poll(2) [as used by nbdcopy] for<br>
copying?  I would imagine that it should make hardly any difference<br>
since we're only using a couple of file descriptors with poll(2) in<br>
nbdcopy, and it's my understanding that poll(2) is only pessimal when<br>
you have to poll large numbers of FDs.<br></blockquote><div><br></div><div>I think that poll or select should be faster for 2 file descriptors, but the</div><div>difference is negligible in this context.</div></div></div></blockquote><div><br></div><div>Some data when copying fredora 32 qcow2 image to nbdkit memory plugin:</div><div><br></div><div>$ strace -c ./copy-libev $SRC $DST<br>% time     seconds  usecs/call     calls    errors syscall<br>------ ----------- ----------- --------- --------- ----------------<br> 50.07    0.271421          17     15728         4 recvfrom<br> 39.10    0.211987          30      6928           sendto<br>  8.25    0.044734           4      9114           epoll_wait<br>  1.13    0.006102           4      1335           epoll_ctl<br>  1.01    0.005484         457        12         6 wait4<br>  0.10    0.000522           4       122           mmap<br>  0.06    0.000311           3        78        48 openat<br>  0.05    0.000298          49         6           clone<br>  0.04    0.000227           5        43           read<br>  0.03    0.000143           3        42           close<br>  0.02    0.000135           2        52           rt_sigprocmask<br>  0.02    0.000132           3        37           rt_sigaction<br>  0.02    0.000105           3        32           mprotect<br>  0.02    0.000100           3        30        15 stat<br>  0.02    0.000095          47         2           execve<br>  0.01    0.000061           2        29           fstat<br>  0.01    0.000044           4         9           poll<br>  0.01    0.000038           2        16           pread64<br>  0.00    0.000027           5         5           pipe<br>  0.00    0.000021           1        13           brk<br>  0.00    0.000019           3         6           rt_sigreturn<br>  0.00    0.000017           1        11         2 ioctl<br>  0.00    0.000015           7         2           munmap<br>  0.00    0.000015           7         2           connect<br>  0.00    0.000010           5         2           socket<br>  0.00    0.000007           3         2           shutdown<br>  0.00    0.000007           7         1           sysinfo<br>  0.00    0.000005           2         2           getgid<br>  0.00    0.000004           0         5         4 access<br>  0.00    0.000004           2         2           getsockopt<br>  0.00    0.000004           4         1           uname<br>  0.00    0.000004           2         2           getuid<br>  0.00    0.000004           2         2           geteuid<br>  0.00    0.000004           2         2           getegid<br>  0.00    0.000004           1         4         2 arch_prctl<br>  0.00    0.000003           1         2         2 setsockopt<br>  0.00    0.000002           0         5           lseek<br>  0.00    0.000000           0         1           dup2<br>  0.00    0.000000           0         2           getpid<br>  0.00    0.000000           0         5         1 fcntl<br>  0.00    0.000000           0         1           getppid<br>  0.00    0.000000           0         1           getpgrp<br>  0.00    0.000000           0         1           set_tid_address<br>  0.00    0.000000           0         1           set_robust_list<br>  0.00    0.000000           0         1           eventfd2<br>  0.00    0.000000           0         1           epoll_create1<br>  0.00    0.000000           0         3           prlimit64<br>  0.00    0.000000           0         1           getrandom<br>------ ----------- ----------- --------- --------- ----------------<br>100.00    0.542115          16     33704        84 total<br></div><div> </div><div>$ strace -f -c ../copy/nbdcopy --sparse=1048576 --request-size=1048576 --requests=16 --connections=1 $SRC $DST<br>strace: Process 1094611 attached<br>strace: Process 1094612 attached<br>strace: Process 1094613 attached<br>strace: Process 1094614 attached<br>strace: Process 1094615 attached<br>strace: Process 1094616 attached<br>strace: Process 1094617 attached<br>strace: Process 1094618 attached<br>strace: Process 1094619 attached<br>strace: Process 1094620 attached<br>strace: Process 1094621 attached<br>strace: Process 1094622 attached<br>strace: Process 1094623 attached<br>strace: Process 1094641 attached</div><div><br></div><div>(Not sure why we start so many threads with --connections=1 - bug?)</div><div><br>% time     seconds  usecs/call     calls    errors syscall<br>------ ----------- ----------- --------- --------- ----------------<br> 58.10    0.881783      146963         6           futex<br> 18.70    0.283841          17     16634         2 recvfrom<br> 17.42    0.264326          31      8398         4 sendto<br>  2.73    0.041494           4      8877           poll<br>  1.97    0.029895        1299        23        10 wait4<br>  0.46    0.006970         240        29           madvise<br>  0.13    0.001903           6       311           mmap<br>  0.09    0.001330         190         7           execve<br>  0.07    0.001041          40        26           munmap<br>  0.05    0.000825           5       157        67 openat<br>  0.04    0.000557           5       100           mprotect<br>  0.04    0.000548           4       113           read<br>  0.04    0.000545          38        14           clone<br>  0.03    0.000440           2       151         6 close<br>  0.02    0.000329           3        97           fstat<br>  0.02    0.000267           2       125           rt_sigprocmask<br>  0.02    0.000251           3        66           pread64<br>  0.01    0.000214           1       148           rt_sigaction<br>  0.01    0.000188          18        10           statfs<br>  0.01    0.000145           5        28           brk<br>  0.01    0.000143           3        37           write<br>  0.01    0.000140           5        24        10 access<br>  0.01    0.000083           1        69        31 stat<br>  0.00    0.000063          10         6           socket<br>  0.00    0.000049           3        15         6 ioctl<br>  0.00    0.000047          15         3         1 lstat<br>  0.00    0.000045           7         6         4 connect<br>  0.00    0.000032           4         8           pipe<br>  0.00    0.000031           2        14         7 arch_prctl<br>  0.00    0.000028           2        10           rt_sigreturn<br>  0.00    0.000023           2         8           prlimit64<br>  0.00    0.000021           3         7           set_robust_list<br>  0.00    0.000019           3         6           set_tid_address<br>  0.00    0.000011          11         1           lgetxattr<br>  0.00    0.000009           4         2           shutdown<br>  0.00    0.000008           1         6           lseek<br>  0.00    0.000007           7         1         1 getxattr<br>  0.00    0.000006           0        15           getpid<br>  0.00    0.000006           6         1           sysinfo<br>  0.00    0.000005           5         1           uname<br>  0.00    0.000004           0        16           dup2<br>  0.00    0.000000           0         2         2 setsockopt<br>  0.00    0.000000           0         2           getsockopt<br>  0.00    0.000000           0         4         1 fcntl<br>  0.00    0.000000           0         1           chdir<br>  0.00    0.000000           0         9           getuid<br>  0.00    0.000000           0         9           getgid<br>  0.00    0.000000           0         9           geteuid<br>  0.00    0.000000           0         9           getegid<br>  0.00    0.000000           0         1           getppid<br>  0.00    0.000000           0         1           getpgrp<br>  0.00    0.000000           0         2           getdents64<br>  0.00    0.000000           0         1           getrandom<br>------ ----------- ----------- --------- --------- ----------------<br>100.00    1.517672          42     35626       152 total<br></div><div><br></div><div>If you want to use poll in libev, you can:</div><div><br></div><div>diff --git a/examples/copy-libev.c b/examples/copy-libev.c<br>index 6e6cbcb..1878f27 100644<br>--- a/examples/copy-libev.c<br>+++ b/examples/copy-libev.c<br>@@ -530,7 +530,7 @@ main (int argc, char *argv[])<br> {<br>     int i;<br> <br>-    loop = EV_DEFAULT;<br>+    loop = ev_loop_new (EVBACKEND_POLL);<br> <br>     if (argc != 3)<br>         FAIL ("Usage: %s src-uri dst-uri", PROG);<br></div><div><br></div><div>But it does not look useful:</div><div><br></div><div>$ strace -c ./copy-libev $SRC $DST<br>% time     seconds  usecs/call     calls    errors syscall<br>------ ----------- ----------- --------- --------- ----------------<br> 48.50    0.261526          16     15626         1 recvfrom<br> 39.01    0.210365          30      7007           sendto<br>  8.24    0.044413           4      9214           poll<br></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_quote"><div>The advantage in using an event loop is an easier way to extend the application,</div><div>for example I'm using async block status, this will be much harder to do in nbdcopy.</div><div><br></div><div>Another advantage is adding more features like handling signals, timeouts,</div><div>child processes, or other other network protocols (e.g. nbd <-> http).</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
Anyway patch series is fine, ACK.<br>
<br>
Rich.<br>
<br>
> Since getting extents is asynchronous, and starting new request depends<br>
> on being able to get the next extent, requests have now a new a waiting<br>
> state. When a request is started when extents are not available, it is<br>
> marked as waiting. When extents request is completed, we start all<br>
> waiting requests.<br>
> <br>
> Here is example log showing whats going on while copying fedora 32<br>
> image:<br>
> <br>
> The first request detect that we don't have extents yet, so it starts<br>
> aync block status request.<br>
> <br>
> copy-libev: r0: start extents offset=0 count=134217728<br>
> copy-libev: r0: received 14 extents for base:allocation<br>
> copy-libev: r0: extents completed time=0.001098<br>
> <br>
> When extent request completes, we start al waiting requests. The first<br>
> request (r0) looked into the first extent (e0) and consumed all of it:<br>
> <br>
> copy-libev: r0: start request offset=0<br>
> copy-libev: e0: offset=0 len=65536 zero=0<br>
> copy-libev: r0: extent offset=0 len=65536 zero=0<br>
> copy-libev: r0: start read offset=0 len=65536<br>
> <br>
> The second request (r15) is looking into the second extent (e1) and<br>
> consume all of it, starting a zero request:<br>
> <br>
> copy-libev: r15: start request offset=65536<br>
> copy-libev: e1: offset=65536 len=983040 zero=1<br>
> copy-libev: r15: extent offset=65536 len=983040 zero=1<br>
> copy-libev: r15: start zero offset=65536 len=983040<br>
> <br>
> ...<br>
> <br>
> Request (r12) looked into the fifth extent (e4), but since this extent<br>
> was large (10747904), it consume only 1m from it:<br>
> <br>
> copy-libev: r12: start request offset=2097152<br>
> copy-libev: e4: offset=2097152 len=10747904 zero=0<br>
> copy-libev: r12: extent offset=2097152 len=1048576 zero=0<br>
> copy-libev: r12: start read offset=2097152 len=1048576<br>
> <br>
> The next request consumed the next 1m from the same extent (e4):<br>
> <br>
> copy-libev: r11: start request offset=3145728<br>
> copy-libev: e4: offset=3145728 len=9699328 zero=0<br>
> copy-libev: r11: extent offset=3145728 len=1048576 zero=0<br>
> copy-libev: r11: start read offset=3145728 len=1048576<br>
> copy-libev: r10: start request offset=4194304<br>
> <br>
> ..<br>
> <br>
> The last part of extent e4 was consumed, and we switched to extent e5:<br>
> <br>
> copy-libev: r2: start request offset=12582912<br>
> copy-libev: e4: offset=12582912 len=262144 zero=0<br>
> copy-libev: r2: extent offset=12582912 len=262144 zero=0<br>
> copy-libev: r2: start read offset=12582912 len=262144<br>
> copy-libev: r1: start request offset=12845056<br>
> copy-libev: e5: offset=12845056 len=131072 zero=1<br>
> copy-libev: r1: extent offset=12845056 len=131072 zero=1<br>
> copy-libev: r1: start zero offset=12845056 len=131072<br>
> <br>
> ...<br>
> <br>
> Request (r11) consumed the last extent (e13), starting a zero request.<br>
> This free the extents array:<br>
> <br>
> copy-libev: r11: start request offset=133955584<br>
> copy-libev: e13: offset=133955584 len=262144 zero=1<br>
> copy-libev: r11: extent offset=133955584 len=262144 zero=1<br>
> copy-libev: r11: consumed all extents offset=134217728<br>
> copy-libev: r11: start zero offset=133955584 len=262144<br>
> <br>
> ...<br>
> <br>
> Request (r12) started when extents array as cleared, so it started new<br>
> block status request:<br>
> <br>
> copy-libev: r12: start extents offset=134217728 count=134217728<br>
> ...<br>
> copy-libev: r12: received 3 extents for base:allocation<br>
> copy-libev: r12: extents completed time=0.003027<br>
> <br>
> ...<br>
> <br>
> The rest of the flow is same as before. When all requests are done,<br>
> we shutdown the event loop and flush:<br>
> <br>
> copy-libev: r4: request completed offset=6438256640 len=1048576 time=0.000132<br>
> copy-libev: r1: read completed offset=6442385408 len=65536<br>
> copy-libev: r1: start write offset=6442385408 len=65536<br>
> copy-libev: r14: request completed offset=6439305216 len=1048576 time=0.000126<br>
> copy-libev: r8: request completed offset=6440353792 len=1048576 time=0.000151<br>
> copy-libev: r2: request completed offset=6441402368 len=983040 time=0.000143<br>
> copy-libev: r1: request completed offset=6442385408 len=65536 time=0.000142<br>
> copy-libev: flush<br>
> <br>
> Signed-off-by: Nir Soffer <<a href="mailto:nsoffer@redhat.com" target="_blank">nsoffer@redhat.com</a>><br>
> ---<br>
>  examples/copy-libev.c | 232 ++++++++++++++++++++++++++++++++++++++++--<br>
>  1 file changed, 224 insertions(+), 8 deletions(-)<br>
> <br>
> diff --git a/examples/copy-libev.c b/examples/copy-libev.c<br>
> index 84d5c03..3030955 100644<br>
> --- a/examples/copy-libev.c<br>
> +++ b/examples/copy-libev.c<br>
> @@ -41,6 +41,7 @@<br>
>   */<br>
>  #define MAX_REQUESTS 16<br>
>  #define REQUEST_SIZE (1024 * 1024)<br>
> +#define EXTENTS_SIZE (128 * 1024 * 1024)<br>
>  <br>
>  #define MIN(a,b) (a) < (b) ? (a) : (b)<br>
>  <br>
> @@ -62,14 +63,18 @@ struct connection {<br>
>      ev_io watcher;<br>
>      struct nbd_handle *nbd;<br>
>      bool can_zero;<br>
> +    bool can_extents;<br>
>  };<br>
>  <br>
>  struct request {<br>
> +    ev_timer watcher;       /* For starting on next loop iteration. */<br>
>      int64_t offset;<br>
>      size_t length;<br>
> +    bool zero;<br>
>      unsigned char *data;<br>
>      size_t index;<br>
>      ev_tstamp started;<br>
> +    bool waiting;           /* Waiting for extents completion. */<br>
>  };<br>
>  <br>
>  static struct ev_loop *loop;<br>
> @@ -77,11 +82,29 @@ static ev_prepare prepare;<br>
>  static struct connection src;<br>
>  static struct connection dst;<br>
>  static struct request requests[MAX_REQUESTS];<br>
> +<br>
> +/* List of extents received from source server. Using the same format returned<br>
> + * by libnbd, array of uint32_t pairs. The first item is the length of the<br>
> + * extent, and the second is the extent flags.<br>
> + *<br>
> + * The number of extents is extents_len / 2. extents_pos is the index of the<br>
> + * current extent.<br>
> + *<br>
> + * extents_in_progress flag is set when we start asynchronous block status<br>
> + * request.<br>
> + */<br>
> +static uint32_t *extents;<br>
> +static size_t extents_len;<br>
> +static size_t extents_pos;<br>
> +static bool extents_in_progress;<br>
> +<br>
>  static int64_t size;<br>
>  static int64_t offset;<br>
>  static int64_t written;<br>
>  static bool debug;<br>
>  <br>
> +static inline void start_request_soon (struct request *r);<br>
> +static void start_request_cb (struct ev_loop *loop, ev_timer *w, int revents);<br>
>  static void start_request(struct request *r);<br>
>  static void start_read(struct request *r);<br>
>  static void start_write(struct request *r);<br>
> @@ -133,23 +156,206 @@ get_events(struct connection *c)<br>
>          default:<br>
>              return 0;<br>
>      }<br>
> +}<br>
> +<br>
> +static int<br>
> +extent_callback (void *user_data, const char *metacontext, uint64_t offset,<br>
> +                 uint32_t *entries, size_t nr_entries, int *error)<br>
> +{<br>
> +    struct request *r = user_data;<br>
> +<br>
> +    if (strcmp (metacontext, LIBNBD_CONTEXT_BASE_ALLOCATION) != 0) {<br>
> +        DEBUG ("Unexpected meta context: %s", metacontext);<br>
> +        return 1;<br>
> +    }<br>
> +<br>
> +    extents = malloc (nr_entries * sizeof *extents);<br>
> +    if (extents == NULL)<br>
> +        FAIL ("Cannot allocated extents: %s", strerror (errno));<br>
> +<br>
> +    memcpy (extents, entries, nr_entries * sizeof *extents);<br>
> +    extents_len = nr_entries;<br>
> +<br>
> +    DEBUG ("r%d: received %d extents for %s",<br>
> +           r->index, nr_entries / 2, metacontext);<br>
> +<br>
> +    return 1;<br>
> +}<br>
> +<br>
> +static int<br>
> +extents_completed (void *user_data, int *error)<br>
> +{<br>
> +    struct request *r = (struct request *)user_data;<br>
> +    int i;<br>
> +<br>
> +    DEBUG ("r%d: extents completed time=%.6f",<br>
> +           r->index, ev_now (loop) - r->started);<br>
> +<br>
> +    extents_in_progress = false;<br>
> +<br>
> +    if (extents == NULL) {<br>
> +        DEBUG ("r%d: received no extents, disabling extents", r->index);<br>
> +        src.can_extents = false;<br>
> +    }<br>
>  <br>
> +    /* Start requests waiting for extents completion on the next loop<br>
> +     * iteration, to avoid deadlock if we need to start a read.<br>
> +     */<br>
> +    for (i = 0; i < MAX_REQUESTS; i++) {<br>
> +        struct request *r = &requests[i];<br>
> +        if (r->waiting) {<br>
> +            r->waiting = false;<br>
> +            start_request_soon (r);<br>
> +       }<br>
> +    }<br>
>  <br>
> +    return 1;<br>
> +}<br>
> +<br>
> +static bool<br>
> +start_extents (struct request *r)<br>
> +{<br>
> +    size_t count = MIN (EXTENTS_SIZE, size - offset);<br>
> +    int64_t cookie;<br>
> +<br>
> +    if (extents_in_progress) {<br>
> +        r->waiting = true;<br>
> +        return true;<br>
> +    }<br>
> +<br>
> +    DEBUG ("r%d: start extents offset=%ld count=%ld", r->index, offset, count);<br>
> +<br>
> +    cookie = nbd_aio_block_status (<br>
> +        src.nbd, count, offset,<br>
> +        (nbd_extent_callback) { .callback=extent_callback,<br>
> +                                .user_data=r },<br>
> +        (nbd_completion_callback) { .callback=extents_completed,<br>
> +                                    .user_data=r },<br>
> +        0);<br>
> +    if (cookie == -1) {<br>
> +        DEBUG ("Cannot get extents: %s", nbd_get_error ());<br>
> +        src.can_extents = false;<br>
> +        return false;<br>
> +    }<br>
> +<br>
> +    r->waiting = true;<br>
> +    extents_in_progress = true;<br>
> +<br>
> +    return true;<br>
> +}<br>
> +<br>
> +/* Return next extent to process. */<br>
> +static void<br>
> +next_extent (struct request *r)<br>
> +{<br>
> +    uint32_t limit = MIN (REQUEST_SIZE, size - offset);<br>
> +    uint32_t length = 0;<br>
> +    bool is_zero;<br>
> +<br>
> +    assert (extents);<br>
> +<br>
> +    is_zero = extents[extents_pos + 1] & LIBNBD_STATE_ZERO;<br>
> +<br>
> +    while (length < limit) {<br>
> +        DEBUG ("e%d: offset=%ld len=%ld zero=%d",<br>
> +               extents_pos / 2, offset, extents[extents_pos], is_zero);<br>
> +<br>
> +        /* If this extent is too large, steal some data from it to<br>
> +         * complete the request.<br>
> +         */<br>
> +        if (length + extents[extents_pos] > limit) {<br>
> +            uint32_t stolen = limit - length;<br>
> +<br>
> +            extents[extents_pos] -= stolen;<br>
> +            length += stolen;<br>
> +            break;<br>
> +        }<br>
> +<br>
> +        /* Consume the entire extent and start looking at the next one. */<br>
> +        length += extents[extents_pos];<br>
> +        extents[extents_pos] = 0;<br>
> +<br>
> +        if (extents_pos + 2 == extents_len)<br>
> +            break;<br>
> +<br>
> +        extents_pos += 2;<br>
> +<br>
> +        /* If next extent is different, we are done. */<br>
> +        if ((extents[extents_pos + 1] & LIBNBD_STATE_ZERO) != is_zero)<br>
> +            break;<br>
> +    }<br>
> +<br>
> +    assert (length > 0 && length <= limit);<br>
> +<br>
> +    r->offset = offset;<br>
> +    r->length = length;<br>
> +    r->zero = is_zero;<br>
> +<br>
> +    DEBUG ("r%d: extent offset=%ld len=%ld zero=%d",<br>
> +           r->index, r->offset, r->length, r->zero);<br>
> +<br>
> +    offset += length;<br>
> +<br>
> +    if (extents_pos + 2 == extents_len && extents[extents_pos] == 0) {<br>
> +        /* Processed all extents, clear extents. */<br>
> +        DEBUG ("r%d: consumed all extents offset=%ld", r->index, offset);<br>
> +        free (extents);<br>
> +        extents = NULL;<br>
> +        extents_pos = 0;<br>
> +        extents_len = 0;<br>
> +    }<br>
> +}<br>
> +<br>
> +static inline void<br>
> +start_request_soon (struct request *r)<br>
> +{<br>
> +    ev_timer_init (&r->watcher, start_request_cb, 0, 0);<br>
> +    ev_timer_start (loop, &r->watcher);<br>
> +}<br>
> +<br>
> +static void<br>
> +start_request_cb (struct ev_loop *loop, ev_timer *w, int revents)<br>
> +{<br>
> +    struct request *r = (struct request *)w;<br>
> +    start_request (r);<br>
>  }<br>
>  <br>
>  /* Start async copy or zero request. */<br>
>  static void<br>
>  start_request(struct request *r)<br>
>  {<br>
> -    assert (offset < size);<br>
> +    /* Cancel the request if we are done. */<br>
> +    if (offset == size)<br>
> +        return;<br>
>  <br>
>      r->started = ev_now (loop);<br>
> -    r->length = MIN (REQUEST_SIZE, size - offset);<br>
> -    r->offset = offset;<br>
>  <br>
> -    start_read (r);<br>
> -<br>
> -    offset += r->length;<br>
> +    /* If needed, get more extents from server. */<br>
> +    if (src.can_extents && extents == NULL && start_extents (r))<br>
> +        return;<br>
> +<br>
> +    DEBUG ("r%d: start request offset=%ld", r->index, offset);<br>
> +<br>
> +    if (src.can_extents) {<br>
> +        /* Handle the next extent. */<br>
> +        next_extent (r);<br>
> +        if (r->zero) {<br>
> +            if (dst.can_zero) {<br>
> +                start_zero (r);<br>
> +            } else {<br>
> +                memset (r->data, 0, r->length);<br>
> +                start_write (r);<br>
> +            }<br>
> +        } else {<br>
> +            start_read (r);<br>
> +        }<br>
> +    } else {<br>
> +        /* Extents not available. */<br>
> +        r->length = MIN (REQUEST_SIZE, size - offset);<br>
> +        r->offset = offset;<br>
> +        start_read (r);<br>
> +        offset += r->length;<br>
> +    }<br>
>  }<br>
>  <br>
>  static void<br>
> @@ -240,9 +446,11 @@ request_completed (void *user_data, int *error)<br>
>          ev_break (loop, EVBREAK_ALL);<br>
>      }<br>
>  <br>
> -    /* If we have data to read, start a new read. */<br>
> +    /* If we have more work, start a new request on the next loop<br>
> +     * iteration, to avoid deadlock if we need to start a zero or write.<br>
> +     */<br>
>      if (offset < size)<br>
> -        start_request(r);<br>
> +        start_request_soon(r);<br>
>  <br>
>      return 1;<br>
>  }<br>
> @@ -304,11 +512,19 @@ main (int argc, char *argv[])<br>
>  <br>
>      debug = getenv ("COPY_LIBEV_DEBUG") != NULL;<br>
>  <br>
> +    /* Configure soruce to report extents. */<br>
> +<br>
> +    if (nbd_add_meta_context (src.nbd, LIBNBD_CONTEXT_BASE_ALLOCATION))<br>
> +        FAIL ("Cannot add base:allocation: %s", nbd_get_error ());<br>
> +<br>
>      /* Connecting is fast, so use the syncronous API. */<br>
>  <br>
>      if (nbd_connect_uri (src.nbd, argv[1]))<br>
>          FAIL ("Cannot connect to source: %s", nbd_get_error ());<br>
>  <br>
> +    src.can_extents = nbd_can_meta_context (<br>
> +        src.nbd, LIBNBD_CONTEXT_BASE_ALLOCATION) > 0;<br>
> +<br>
>      if (nbd_connect_uri (dst.nbd, argv[2]))<br>
>          FAIL ("Cannot connect to destination: %s", nbd_get_error ());<br>
>  <br>
> -- <br>
> 2.26.2<br>
<br>
-- <br>
Richard Jones, Virtualization Group, Red Hat <a href="http://people.redhat.com/~rjones" rel="noreferrer" target="_blank">http://people.redhat.com/~rjones</a><br>
Read my programming and virtualization blog: <a href="http://rwmj.wordpress.com" rel="noreferrer" target="_blank">http://rwmj.wordpress.com</a><br>
virt-builder quickly builds VMs from scratch<br>
<a href="http://libguestfs.org/virt-builder.1.html" rel="noreferrer" target="_blank">http://libguestfs.org/virt-builder.1.html</a><br>
<br>
</blockquote></div></div>
</blockquote></div></div>