From 9bc9732faeff09828fe38c0ebe2401ee131a6fca Mon Sep 17 00:00:00 2001 From: Changlong Xie Date: Wed, 12 Oct 2016 18:18:28 +0800 Subject: nbd: Use CoQueue for free_sema instead of CoMutex NBD is using the CoMutex in a way that wasn't anticipated. For example, if there are N(N=26, MAX_NBD_REQUESTS=16) nbd write requests, so we will invoke nbd_client_co_pwritev N times. ---------------------------------------------------------------------------------------- time request Actions 1 1 in_flight=1, Coroutine=C1 2 2 in_flight=2, Coroutine=C2 ... 15 15 in_flight=15, Coroutine=C15 16 16 in_flight=16, Coroutine=C16, free_sema->holder=C16, mutex->locked=true 17 17 in_flight=16, Coroutine=C17, queue C17 into free_sema->queue 18 18 in_flight=16, Coroutine=C18, queue C18 into free_sema->queue ... 26 N in_flight=16, Coroutine=C26, queue C26 into free_sema->queue ---------------------------------------------------------------------------------------- Once nbd client recieves request No.16' reply, we will re-enter C16. It's ok, because it's equal to 'free_sema->holder'. ---------------------------------------------------------------------------------------- time request Actions 27 16 in_flight=15, Coroutine=C16, free_sema->holder=C16, mutex->locked=false ---------------------------------------------------------------------------------------- Then nbd_coroutine_end invokes qemu_co_mutex_unlock what will pop coroutines from free_sema->queue's head and enter C17. More free_sema->holder is C17 now. ---------------------------------------------------------------------------------------- time request Actions 28 17 in_flight=16, Coroutine=C17, free_sema->holder=C17, mutex->locked=true ---------------------------------------------------------------------------------------- In above scenario, we only recieves request No.16' reply. As time goes by, nbd client will almostly recieves replies from requests 1 to 15 rather than request 17 who owns C17. In this case, we will encounter assert "mutex->holder == self" failed since Kevin's commit 0e438cdc "coroutine: Let CoMutex remember who holds it". For example, if nbd client recieves request No.15' reply, qemu will stop unexpectedly: ---------------------------------------------------------------------------------------- time request Actions 29 15(most case) in_flight=15, Coroutine=C15, free_sema->holder=C17, mutex->locked=false ---------------------------------------------------------------------------------------- Per Paolo's suggestion "The simplest fix is to change it to CoQueue, which is like a condition variable", this patch replaces CoMutex with CoQueue. Cc: Wen Congyang Reported-by: zhanghailiang Suggested-by: Paolo Bonzini Signed-off-by: Changlong Xie Message-Id: <1476267508-19499-1-git-send-email-xiecl.fnst@cn.fujitsu.com> Signed-off-by: Paolo Bonzini --- block/nbd-client.c | 8 ++++---- block/nbd-client.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'block') diff --git a/block/nbd-client.c b/block/nbd-client.c index 2cf3237ef3..40b28ab00b 100644 --- a/block/nbd-client.c +++ b/block/nbd-client.c @@ -199,8 +199,8 @@ static void nbd_coroutine_start(NbdClientSession *s, { /* Poor man semaphore. The free_sema is locked when no other request * can be accepted, and unlocked after receiving one reply. */ - if (s->in_flight >= MAX_NBD_REQUESTS - 1) { - qemu_co_mutex_lock(&s->free_sema); + if (s->in_flight == MAX_NBD_REQUESTS) { + qemu_co_queue_wait(&s->free_sema); assert(s->in_flight < MAX_NBD_REQUESTS); } s->in_flight++; @@ -214,7 +214,7 @@ static void nbd_coroutine_end(NbdClientSession *s, int i = HANDLE_TO_INDEX(s, request->handle); s->recv_coroutine[i] = NULL; if (s->in_flight-- == MAX_NBD_REQUESTS) { - qemu_co_mutex_unlock(&s->free_sema); + qemu_co_queue_next(&s->free_sema); } } @@ -386,7 +386,7 @@ int nbd_client_init(BlockDriverState *bs, } qemu_co_mutex_init(&client->send_mutex); - qemu_co_mutex_init(&client->free_sema); + qemu_co_queue_init(&client->free_sema); client->sioc = sioc; object_ref(OBJECT(client->sioc)); diff --git a/block/nbd-client.h b/block/nbd-client.h index 044aca4530..307b8b1e99 100644 --- a/block/nbd-client.h +++ b/block/nbd-client.h @@ -24,7 +24,7 @@ typedef struct NbdClientSession { off_t size; CoMutex send_mutex; - CoMutex free_sema; + CoQueue free_sema; Coroutine *send_coroutine; int in_flight; -- cgit v1.2.3 From b626b51a6721e53817155af720243f59072e424f Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Fri, 14 Oct 2016 13:33:04 -0500 Subject: nbd: Treat flags vs. command type as separate fields Current upstream NBD documents that requests have a 16-bit flags, followed by a 16-bit type integer; although older versions mentioned only a 32-bit field with masking to find flags. Since the protocol is in network order (big-endian over the wire), the ABI is unchanged; but dealing with the flags as a separate field rather than masking will make it easier to add support for upcoming NBD extensions that increase the number of both flags and commands. Improve some comments in nbd.h based on the current upstream NBD protocol (https://github.com/yoe/nbd/blob/master/doc/proto.md), and touch some nearby code to keep checkpatch.pl happy. Signed-off-by: Eric Blake Message-Id: <1476469998-28592-3-git-send-email-eblake@redhat.com> Signed-off-by: Paolo Bonzini --- block/nbd-client.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'block') diff --git a/block/nbd-client.c b/block/nbd-client.c index 40b28ab00b..8ca503015c 100644 --- a/block/nbd-client.c +++ b/block/nbd-client.c @@ -1,6 +1,7 @@ /* * QEMU Block driver for NBD * + * Copyright (C) 2016 Red Hat, Inc. * Copyright (C) 2008 Bull S.A.S. * Author: Laurent Vivier * @@ -258,7 +259,7 @@ int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset, if (flags & BDRV_REQ_FUA) { assert(client->nbdflags & NBD_FLAG_SEND_FUA); - request.type |= NBD_CMD_FLAG_FUA; + request.flags |= NBD_CMD_FLAG_FUA; } assert(bytes <= NBD_MAX_BUFFER_SIZE); @@ -343,11 +344,7 @@ void nbd_client_attach_aio_context(BlockDriverState *bs, void nbd_client_close(BlockDriverState *bs) { NbdClientSession *client = nbd_get_client_session(bs); - struct nbd_request request = { - .type = NBD_CMD_DISC, - .from = 0, - .len = 0 - }; + struct nbd_request request = { .type = NBD_CMD_DISC }; if (client->ioc == NULL) { return; -- cgit v1.2.3 From 10676b81a9a69e9660fc203e3fe7d61a80ef1089 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Fri, 14 Oct 2016 13:33:06 -0500 Subject: nbd: Rename NbdClientSession to NBDClientSession It's better to use consistent capitalization of the namespace used for NBD functions; we have more instances of NBD* than Nbd*. Signed-off-by: Eric Blake Message-Id: <1476469998-28592-5-git-send-email-eblake@redhat.com> Signed-off-by: Paolo Bonzini --- block/nbd-client.c | 26 +++++++++++++------------- block/nbd-client.h | 6 +++--- block/nbd.c | 4 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) (limited to 'block') diff --git a/block/nbd-client.c b/block/nbd-client.c index 8ca503015c..e6fe603ef3 100644 --- a/block/nbd-client.c +++ b/block/nbd-client.c @@ -33,7 +33,7 @@ #define HANDLE_TO_INDEX(bs, handle) ((handle) ^ ((uint64_t)(intptr_t)bs)) #define INDEX_TO_HANDLE(bs, index) ((index) ^ ((uint64_t)(intptr_t)bs)) -static void nbd_recv_coroutines_enter_all(NbdClientSession *s) +static void nbd_recv_coroutines_enter_all(NBDClientSession *s) { int i; @@ -46,7 +46,7 @@ static void nbd_recv_coroutines_enter_all(NbdClientSession *s) static void nbd_teardown_connection(BlockDriverState *bs) { - NbdClientSession *client = nbd_get_client_session(bs); + NBDClientSession *client = nbd_get_client_session(bs); if (!client->ioc) { /* Already closed */ return; @@ -68,7 +68,7 @@ static void nbd_teardown_connection(BlockDriverState *bs) static void nbd_reply_ready(void *opaque) { BlockDriverState *bs = opaque; - NbdClientSession *s = nbd_get_client_session(bs); + NBDClientSession *s = nbd_get_client_session(bs); uint64_t i; int ret; @@ -119,7 +119,7 @@ static int nbd_co_send_request(BlockDriverState *bs, struct nbd_request *request, QEMUIOVector *qiov) { - NbdClientSession *s = nbd_get_client_session(bs); + NBDClientSession *s = nbd_get_client_session(bs); AioContext *aio_context; int rc, ret, i; @@ -167,7 +167,7 @@ static int nbd_co_send_request(BlockDriverState *bs, return rc; } -static void nbd_co_receive_reply(NbdClientSession *s, +static void nbd_co_receive_reply(NBDClientSession *s, struct nbd_request *request, struct nbd_reply *reply, QEMUIOVector *qiov) @@ -195,7 +195,7 @@ static void nbd_co_receive_reply(NbdClientSession *s, } } -static void nbd_coroutine_start(NbdClientSession *s, +static void nbd_coroutine_start(NBDClientSession *s, struct nbd_request *request) { /* Poor man semaphore. The free_sema is locked when no other request @@ -209,7 +209,7 @@ static void nbd_coroutine_start(NbdClientSession *s, /* s->recv_coroutine[i] is set as soon as we get the send_lock. */ } -static void nbd_coroutine_end(NbdClientSession *s, +static void nbd_coroutine_end(NBDClientSession *s, struct nbd_request *request) { int i = HANDLE_TO_INDEX(s, request->handle); @@ -222,7 +222,7 @@ static void nbd_coroutine_end(NbdClientSession *s, int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags) { - NbdClientSession *client = nbd_get_client_session(bs); + NBDClientSession *client = nbd_get_client_session(bs); struct nbd_request request = { .type = NBD_CMD_READ, .from = offset, @@ -248,7 +248,7 @@ int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset, int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags) { - NbdClientSession *client = nbd_get_client_session(bs); + NBDClientSession *client = nbd_get_client_session(bs); struct nbd_request request = { .type = NBD_CMD_WRITE, .from = offset, @@ -277,7 +277,7 @@ int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset, int nbd_client_co_flush(BlockDriverState *bs) { - NbdClientSession *client = nbd_get_client_session(bs); + NBDClientSession *client = nbd_get_client_session(bs); struct nbd_request request = { .type = NBD_CMD_FLUSH }; struct nbd_reply reply; ssize_t ret; @@ -302,7 +302,7 @@ int nbd_client_co_flush(BlockDriverState *bs) int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int count) { - NbdClientSession *client = nbd_get_client_session(bs); + NBDClientSession *client = nbd_get_client_session(bs); struct nbd_request request = { .type = NBD_CMD_TRIM, .from = offset, @@ -343,7 +343,7 @@ void nbd_client_attach_aio_context(BlockDriverState *bs, void nbd_client_close(BlockDriverState *bs) { - NbdClientSession *client = nbd_get_client_session(bs); + NBDClientSession *client = nbd_get_client_session(bs); struct nbd_request request = { .type = NBD_CMD_DISC }; if (client->ioc == NULL) { @@ -362,7 +362,7 @@ int nbd_client_init(BlockDriverState *bs, const char *hostname, Error **errp) { - NbdClientSession *client = nbd_get_client_session(bs); + NBDClientSession *client = nbd_get_client_session(bs); int ret; /* NBD handshake */ diff --git a/block/nbd-client.h b/block/nbd-client.h index 307b8b1e99..d86dd22e35 100644 --- a/block/nbd-client.h +++ b/block/nbd-client.h @@ -17,7 +17,7 @@ #define MAX_NBD_REQUESTS 16 -typedef struct NbdClientSession { +typedef struct NBDClientSession { QIOChannelSocket *sioc; /* The master data channel */ QIOChannel *ioc; /* The current I/O channel which may differ (eg TLS) */ uint16_t nbdflags; @@ -32,9 +32,9 @@ typedef struct NbdClientSession { struct nbd_reply reply; bool is_unix; -} NbdClientSession; +} NBDClientSession; -NbdClientSession *nbd_get_client_session(BlockDriverState *bs); +NBDClientSession *nbd_get_client_session(BlockDriverState *bs); int nbd_client_init(BlockDriverState *bs, QIOChannelSocket *sock, diff --git a/block/nbd.c b/block/nbd.c index 6e837f80c9..b281484648 100644 --- a/block/nbd.c +++ b/block/nbd.c @@ -44,7 +44,7 @@ #define EN_OPTSTR ":exportname=" typedef struct BDRVNBDState { - NbdClientSession client; + NBDClientSession client; /* For nbd_refresh_filename() */ SocketAddress *saddr; @@ -294,7 +294,7 @@ done: return saddr; } -NbdClientSession *nbd_get_client_session(BlockDriverState *bs) +NBDClientSession *nbd_get_client_session(BlockDriverState *bs) { BDRVNBDState *s = bs->opaque; return &s->client; -- cgit v1.2.3 From ed2dd91267a813e4ac5619a2fb53f55c005fcfa6 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Fri, 14 Oct 2016 13:33:07 -0500 Subject: nbd: Rename struct nbd_request and nbd_reply Our coding convention prefers CamelCase names, and we already have other existing structs with NBDFoo naming. Let's be consistent, before later patches add even more structs. Signed-off-by: Eric Blake Message-Id: <1476469998-28592-6-git-send-email-eblake@redhat.com> Signed-off-by: Paolo Bonzini --- block/nbd-client.c | 28 ++++++++++++++-------------- block/nbd-client.h | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) (limited to 'block') diff --git a/block/nbd-client.c b/block/nbd-client.c index e6fe603ef3..3e5f9f5b77 100644 --- a/block/nbd-client.c +++ b/block/nbd-client.c @@ -116,7 +116,7 @@ static void nbd_restart_write(void *opaque) } static int nbd_co_send_request(BlockDriverState *bs, - struct nbd_request *request, + NBDRequest *request, QEMUIOVector *qiov) { NBDClientSession *s = nbd_get_client_session(bs); @@ -168,8 +168,8 @@ static int nbd_co_send_request(BlockDriverState *bs, } static void nbd_co_receive_reply(NBDClientSession *s, - struct nbd_request *request, - struct nbd_reply *reply, + NBDRequest *request, + NBDReply *reply, QEMUIOVector *qiov) { int ret; @@ -196,7 +196,7 @@ static void nbd_co_receive_reply(NBDClientSession *s, } static void nbd_coroutine_start(NBDClientSession *s, - struct nbd_request *request) + NBDRequest *request) { /* Poor man semaphore. The free_sema is locked when no other request * can be accepted, and unlocked after receiving one reply. */ @@ -210,7 +210,7 @@ static void nbd_coroutine_start(NBDClientSession *s, } static void nbd_coroutine_end(NBDClientSession *s, - struct nbd_request *request) + NBDRequest *request) { int i = HANDLE_TO_INDEX(s, request->handle); s->recv_coroutine[i] = NULL; @@ -223,12 +223,12 @@ int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags) { NBDClientSession *client = nbd_get_client_session(bs); - struct nbd_request request = { + NBDRequest request = { .type = NBD_CMD_READ, .from = offset, .len = bytes, }; - struct nbd_reply reply; + NBDReply reply; ssize_t ret; assert(bytes <= NBD_MAX_BUFFER_SIZE); @@ -249,12 +249,12 @@ int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags) { NBDClientSession *client = nbd_get_client_session(bs); - struct nbd_request request = { + NBDRequest request = { .type = NBD_CMD_WRITE, .from = offset, .len = bytes, }; - struct nbd_reply reply; + NBDReply reply; ssize_t ret; if (flags & BDRV_REQ_FUA) { @@ -278,8 +278,8 @@ int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset, int nbd_client_co_flush(BlockDriverState *bs) { NBDClientSession *client = nbd_get_client_session(bs); - struct nbd_request request = { .type = NBD_CMD_FLUSH }; - struct nbd_reply reply; + NBDRequest request = { .type = NBD_CMD_FLUSH }; + NBDReply reply; ssize_t ret; if (!(client->nbdflags & NBD_FLAG_SEND_FLUSH)) { @@ -303,12 +303,12 @@ int nbd_client_co_flush(BlockDriverState *bs) int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int count) { NBDClientSession *client = nbd_get_client_session(bs); - struct nbd_request request = { + NBDRequest request = { .type = NBD_CMD_TRIM, .from = offset, .len = count, }; - struct nbd_reply reply; + NBDReply reply; ssize_t ret; if (!(client->nbdflags & NBD_FLAG_SEND_TRIM)) { @@ -344,7 +344,7 @@ void nbd_client_attach_aio_context(BlockDriverState *bs, void nbd_client_close(BlockDriverState *bs) { NBDClientSession *client = nbd_get_client_session(bs); - struct nbd_request request = { .type = NBD_CMD_DISC }; + NBDRequest request = { .type = NBD_CMD_DISC }; if (client->ioc == NULL) { return; diff --git a/block/nbd-client.h b/block/nbd-client.h index d86dd22e35..51be419405 100644 --- a/block/nbd-client.h +++ b/block/nbd-client.h @@ -29,7 +29,7 @@ typedef struct NBDClientSession { int in_flight; Coroutine *recv_coroutine[MAX_NBD_REQUESTS]; - struct nbd_reply reply; + NBDReply reply; bool is_unix; } NBDClientSession; -- cgit v1.2.3 From fa778fffdfafce811bba3dd97de41fb524b861f7 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Fri, 14 Oct 2016 13:33:18 -0500 Subject: nbd: Implement NBD_CMD_WRITE_ZEROES on client Upstream NBD protocol recently added the ability to efficiently write zeroes without having to send the zeroes over the wire, along with a flag to control whether the client wants a hole. The generic block code takes care of falling back to the obvious write of lots of zeroes if we return -ENOTSUP because the server does not have WRITE_ZEROES. Ideally, since NBD_CMD_WRITE_ZEROES does not involve any data over the wire, we want to support transactions that are much larger than the normal 32M limit imposed on NBD_CMD_WRITE. But the server may still have a limit smaller than UINT_MAX, so until experimental NBD protocol additions for advertising various command sizes is finalized (see [1], [2]), for now we just stick to the same limits as normal writes. [1] https://github.com/yoe/nbd/blob/extension-info/doc/proto.md [2] https://sourceforge.net/p/nbd/mailman/message/35081223/ Signed-off-by: Eric Blake Message-Id: <1476469998-28592-17-git-send-email-eblake@redhat.com> Signed-off-by: Paolo Bonzini --- block/nbd-client.c | 35 +++++++++++++++++++++++++++++++++++ block/nbd-client.h | 2 ++ block/nbd.c | 4 ++++ 3 files changed, 41 insertions(+) (limited to 'block') diff --git a/block/nbd-client.c b/block/nbd-client.c index 3e5f9f5b77..2a302de674 100644 --- a/block/nbd-client.c +++ b/block/nbd-client.c @@ -275,6 +275,41 @@ int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset, return -reply.error; } +int nbd_client_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset, + int count, BdrvRequestFlags flags) +{ + ssize_t ret; + NBDClientSession *client = nbd_get_client_session(bs); + NBDRequest request = { + .type = NBD_CMD_WRITE_ZEROES, + .from = offset, + .len = count, + }; + NBDReply reply; + + if (!(client->nbdflags & NBD_FLAG_SEND_WRITE_ZEROES)) { + return -ENOTSUP; + } + + if (flags & BDRV_REQ_FUA) { + assert(client->nbdflags & NBD_FLAG_SEND_FUA); + request.flags |= NBD_CMD_FLAG_FUA; + } + if (!(flags & BDRV_REQ_MAY_UNMAP)) { + request.flags |= NBD_CMD_FLAG_NO_HOLE; + } + + nbd_coroutine_start(client, &request); + ret = nbd_co_send_request(bs, &request, NULL); + if (ret < 0) { + reply.error = -ret; + } else { + nbd_co_receive_reply(client, &request, &reply, NULL); + } + nbd_coroutine_end(client, &request); + return -reply.error; +} + int nbd_client_co_flush(BlockDriverState *bs) { NBDClientSession *client = nbd_get_client_session(bs); diff --git a/block/nbd-client.h b/block/nbd-client.h index 51be419405..f8d6006849 100644 --- a/block/nbd-client.h +++ b/block/nbd-client.h @@ -48,6 +48,8 @@ int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int count); int nbd_client_co_flush(BlockDriverState *bs); int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags); +int nbd_client_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset, + int count, BdrvRequestFlags flags); int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags); diff --git a/block/nbd.c b/block/nbd.c index b281484648..9cff8396f9 100644 --- a/block/nbd.c +++ b/block/nbd.c @@ -466,6 +466,7 @@ static int nbd_co_flush(BlockDriverState *bs) static void nbd_refresh_limits(BlockDriverState *bs, Error **errp) { bs->bl.max_pdiscard = NBD_MAX_BUFFER_SIZE; + bs->bl.max_pwrite_zeroes = NBD_MAX_BUFFER_SIZE; bs->bl.max_transfer = NBD_MAX_BUFFER_SIZE; } @@ -558,6 +559,7 @@ static BlockDriver bdrv_nbd = { .bdrv_file_open = nbd_open, .bdrv_co_preadv = nbd_client_co_preadv, .bdrv_co_pwritev = nbd_client_co_pwritev, + .bdrv_co_pwrite_zeroes = nbd_client_co_pwrite_zeroes, .bdrv_close = nbd_close, .bdrv_co_flush_to_os = nbd_co_flush, .bdrv_co_pdiscard = nbd_client_co_pdiscard, @@ -576,6 +578,7 @@ static BlockDriver bdrv_nbd_tcp = { .bdrv_file_open = nbd_open, .bdrv_co_preadv = nbd_client_co_preadv, .bdrv_co_pwritev = nbd_client_co_pwritev, + .bdrv_co_pwrite_zeroes = nbd_client_co_pwrite_zeroes, .bdrv_close = nbd_close, .bdrv_co_flush_to_os = nbd_co_flush, .bdrv_co_pdiscard = nbd_client_co_pdiscard, @@ -594,6 +597,7 @@ static BlockDriver bdrv_nbd_unix = { .bdrv_file_open = nbd_open, .bdrv_co_preadv = nbd_client_co_preadv, .bdrv_co_pwritev = nbd_client_co_pwritev, + .bdrv_co_pwrite_zeroes = nbd_client_co_pwrite_zeroes, .bdrv_close = nbd_close, .bdrv_co_flush_to_os = nbd_co_flush, .bdrv_co_pdiscard = nbd_client_co_pdiscard, -- cgit v1.2.3