From: Sagi Grimberg <sagig-LDSdmyG8hGV8YrgS2mwiifqBs+8SCbDb@public.gmane.org>
To: Chuck Lever <chuck.lever-QHcLZuEGTsvQT0dZR+AlfA@public.gmane.org>,
linux-nfs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-rdma-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Subject: Re: [PATCH 7/8] xprtrdma: Split the completion queue
Date: Wed, 16 Apr 2014 15:48:28 +0300 [thread overview]
Message-ID: <534E7C1C.5070407@dev.mellanox.co.il> (raw)
In-Reply-To: <20140414222323.20646.66946.stgit-FYjufvaPoItvLzlybtyyYzGyq/o6K9yX@public.gmane.org>
On 4/15/2014 1:23 AM, Chuck Lever wrote:
> The current CQ handler uses the ib_wc.opcode field to distinguish
> between event types. However, the contents of that field are not
> reliable if the completion status is not IB_WC_SUCCESS.
>
> When an error completion occurs on a send event, the CQ handler
> schedules a tasklet with something that is not a struct rpcrdma_rep.
> This is never correct behavior, and sometimes it results in a panic.
>
> To resolve this issue, split the completion queue into a send CQ and
> a receive CQ. The send CQ handler now handles only struct rpcrdma_mw
> wr_id's, and the receive CQ handler now handles only struct
> rpcrdma_rep wr_id's.
Hey Chuck,
So 2 suggestions related (although not directly) to this one.
1. I recommend suppressing Fastreg completions - no one cares that they
succeeded.
2. If I understood correctly, I see that the CQs are loop-polled in an
interrupt context.
This may become problematic in stress workload where the CQ simply
never drains (imagine
some studios task keeps posting WRs as soon as IOs complete). This
will cause CQ poll loop
to go on forever. This situation may cause starvation of other CQs
that share the same EQ (CPU
is hogged by the endless poller).
This may be solved in 2 ways:
- Use blk-iopoll to maintain fairness - the downside will be moving
from interrupt context to soft-irq (slower).
- Set some configurable budget to CQ poller - after finishing the
budget, notify the CQ and bail.
If there is another CQ waiting to get in - it's only fair that it
will be given with a chance, else another interrupt
on that CQ will immediately come.
I noticed that one with iSER which polls from tasklet context
(under heavy workload).
Sagi.
> Fix suggested by Shirley Ma <shirley.ma-QHcLZuEGTsvQT0dZR+AlfA@public.gmane.org>
>
> Reported-by: Rafael Reiter <rafael.reiter-cv18SyjCLaheoWH0uzbU5w@public.gmane.org>
> Fixes: 5c635e09cec0feeeb310968e51dad01040244851
> BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=73211
> Tested-by: Klemens Senn <klemens.senn-cv18SyjCLaheoWH0uzbU5w@public.gmane.org>
> Signed-off-by: Chuck Lever <chuck.lever-QHcLZuEGTsvQT0dZR+AlfA@public.gmane.org>
> ---
>
> net/sunrpc/xprtrdma/verbs.c | 228 +++++++++++++++++++++++----------------
> net/sunrpc/xprtrdma/xprt_rdma.h | 1
> 2 files changed, 135 insertions(+), 94 deletions(-)
>
> diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c
> index 0f8c22c..35f5ab6 100644
> --- a/net/sunrpc/xprtrdma/verbs.c
> +++ b/net/sunrpc/xprtrdma/verbs.c
> @@ -142,96 +142,113 @@ rpcrdma_cq_async_error_upcall(struct ib_event *event, void *context)
> }
> }
>
> -static inline
> -void rpcrdma_event_process(struct ib_wc *wc)
> +static void
> +rpcrdma_send_event_process(struct ib_wc *wc)
> {
> - struct rpcrdma_mw *frmr;
> - struct rpcrdma_rep *rep =
> - (struct rpcrdma_rep *)(unsigned long) wc->wr_id;
> + struct rpcrdma_mw *frmr = (struct rpcrdma_mw *)(unsigned long)wc->wr_id;
>
> - dprintk("RPC: %s: event rep %p status %X opcode %X length %u\n",
> - __func__, rep, wc->status, wc->opcode, wc->byte_len);
> + dprintk("RPC: %s: frmr %p status %X opcode %d\n",
> + __func__, frmr, wc->status, wc->opcode);
>
> - if (!rep) /* send completion that we don't care about */
> + if (wc->wr_id == 0ULL)
> return;
> -
> - if (IB_WC_SUCCESS != wc->status) {
> - dprintk("RPC: %s: WC opcode %d status %X, connection lost\n",
> - __func__, wc->opcode, wc->status);
> - rep->rr_len = ~0U;
> - if (wc->opcode != IB_WC_FAST_REG_MR && wc->opcode != IB_WC_LOCAL_INV)
> - rpcrdma_schedule_tasklet(rep);
> + if (wc->status != IB_WC_SUCCESS)
> return;
> - }
>
> - switch (wc->opcode) {
> - case IB_WC_FAST_REG_MR:
> - frmr = (struct rpcrdma_mw *)(unsigned long)wc->wr_id;
> + if (wc->opcode == IB_WC_FAST_REG_MR)
> frmr->r.frmr.state = FRMR_IS_VALID;
> - break;
> - case IB_WC_LOCAL_INV:
> - frmr = (struct rpcrdma_mw *)(unsigned long)wc->wr_id;
> + else if (wc->opcode == IB_WC_LOCAL_INV)
> frmr->r.frmr.state = FRMR_IS_INVALID;
> - break;
> - case IB_WC_RECV:
> - rep->rr_len = wc->byte_len;
> - ib_dma_sync_single_for_cpu(
> - rdmab_to_ia(rep->rr_buffer)->ri_id->device,
> - rep->rr_iov.addr, rep->rr_len, DMA_FROM_DEVICE);
> - /* Keep (only) the most recent credits, after check validity */
> - if (rep->rr_len >= 16) {
> - struct rpcrdma_msg *p =
> - (struct rpcrdma_msg *) rep->rr_base;
> - unsigned int credits = ntohl(p->rm_credit);
> - if (credits == 0) {
> - dprintk("RPC: %s: server"
> - " dropped credits to 0!\n", __func__);
> - /* don't deadlock */
> - credits = 1;
> - } else if (credits > rep->rr_buffer->rb_max_requests) {
> - dprintk("RPC: %s: server"
> - " over-crediting: %d (%d)\n",
> - __func__, credits,
> - rep->rr_buffer->rb_max_requests);
> - credits = rep->rr_buffer->rb_max_requests;
> - }
> - atomic_set(&rep->rr_buffer->rb_credits, credits);
> - }
> - rpcrdma_schedule_tasklet(rep);
> - break;
> - default:
> - dprintk("RPC: %s: unexpected WC event %X\n",
> - __func__, wc->opcode);
> - break;
> - }
> }
>
> -static inline int
> -rpcrdma_cq_poll(struct ib_cq *cq)
> +static int
> +rpcrdma_sendcq_poll(struct ib_cq *cq)
> {
> struct ib_wc wc;
> int rc;
>
> - for (;;) {
> - rc = ib_poll_cq(cq, 1, &wc);
> - if (rc < 0) {
> - dprintk("RPC: %s: ib_poll_cq failed %i\n",
> - __func__, rc);
> - return rc;
> - }
> - if (rc == 0)
> - break;
> + while ((rc = ib_poll_cq(cq, 1, &wc)) == 1)
> + rpcrdma_send_event_process(&wc);
> + return rc;
> +}
>
> - rpcrdma_event_process(&wc);
> +/*
> + * Handle send, fast_reg_mr, and local_inv completions.
> + *
> + * Send events are typically suppressed and thus do not result
> + * in an upcall. Occasionally one is signaled, however. This
> + * prevents the provider's completion queue from wrapping and
> + * losing a completion.
> + */
> +static void
> +rpcrdma_sendcq_upcall(struct ib_cq *cq, void *cq_context)
> +{
> + int rc;
> +
> + rc = rpcrdma_sendcq_poll(cq);
> + if (rc) {
> + dprintk("RPC: %s: ib_poll_cq failed: %i\n",
> + __func__, rc);
> + return;
> }
> + rc = ib_req_notify_cq(cq, IB_CQ_NEXT_COMP);
> + if (rc) {
> + dprintk("RPC: %s: ib_req_notify_cq failed: %i\n",
> + __func__, rc);
> + return;
> + }
> + rpcrdma_sendcq_poll(cq);
> +}
>
> - return 0;
> +static void
> +rpcrdma_recv_event_process(struct ib_wc *wc)
> +{
> + struct rpcrdma_rep *rep =
> + (struct rpcrdma_rep *)(unsigned long)wc->wr_id;
> +
> + dprintk("RPC: %s: rep %p status %X opcode %X length %u\n",
> + __func__, rep, wc->status, wc->opcode, wc->byte_len);
> +
> + if (wc->status != IB_WC_SUCCESS) {
> + rep->rr_len = ~0U;
> + goto out_schedule;
> + }
> + if (wc->opcode != IB_WC_RECV)
> + return;
> +
> + rep->rr_len = wc->byte_len;
> + ib_dma_sync_single_for_cpu(rdmab_to_ia(rep->rr_buffer)->ri_id->device,
> + rep->rr_iov.addr, rep->rr_len, DMA_FROM_DEVICE);
> +
> + if (rep->rr_len >= 16) {
> + struct rpcrdma_msg *p = (struct rpcrdma_msg *)rep->rr_base;
> + unsigned int credits = ntohl(p->rm_credit);
> +
> + if (credits == 0)
> + credits = 1; /* don't deadlock */
> + else if (credits > rep->rr_buffer->rb_max_requests)
> + credits = rep->rr_buffer->rb_max_requests;
> + atomic_set(&rep->rr_buffer->rb_credits, credits);
> + }
> +
> +out_schedule:
> + rpcrdma_schedule_tasklet(rep);
> +}
> +
> +static int
> +rpcrdma_recvcq_poll(struct ib_cq *cq)
> +{
> + struct ib_wc wc;
> + int rc;
> +
> + while ((rc = ib_poll_cq(cq, 1, &wc)) == 1)
> + rpcrdma_recv_event_process(&wc);
> + return rc;
> }
>
> /*
> - * rpcrdma_cq_event_upcall
> + * Handle receive completions.
> *
> - * This upcall handles recv and send events.
> * It is reentrant but processes single events in order to maintain
> * ordering of receives to keep server credits.
> *
> @@ -240,26 +257,25 @@ rpcrdma_cq_poll(struct ib_cq *cq)
> * connection shutdown. That is, the structures required for
> * the completion of the reply handler must remain intact until
> * all memory has been reclaimed.
> - *
> - * Note that send events are suppressed and do not result in an upcall.
> */
> static void
> -rpcrdma_cq_event_upcall(struct ib_cq *cq, void *context)
> +rpcrdma_recvcq_upcall(struct ib_cq *cq, void *cq_context)
> {
> int rc;
>
> - rc = rpcrdma_cq_poll(cq);
> - if (rc)
> + rc = rpcrdma_recvcq_poll(cq);
> + if (rc) {
> + dprintk("RPC: %s: ib_poll_cq failed: %i\n",
> + __func__, rc);
> return;
> -
> + }
> rc = ib_req_notify_cq(cq, IB_CQ_NEXT_COMP);
> if (rc) {
> - dprintk("RPC: %s: ib_req_notify_cq failed %i\n",
> + dprintk("RPC: %s: ib_req_notify_cq failed: %i\n",
> __func__, rc);
> return;
> }
> -
> - rpcrdma_cq_poll(cq);
> + rpcrdma_recvcq_poll(cq);
> }
>
> #ifdef RPC_DEBUG
> @@ -613,6 +629,7 @@ rpcrdma_ep_create(struct rpcrdma_ep *ep, struct rpcrdma_ia *ia,
> struct rpcrdma_create_data_internal *cdata)
> {
> struct ib_device_attr devattr;
> + struct ib_cq *sendcq, *recvcq;
> int rc, err;
>
> rc = ib_query_device(ia->ri_id->device, &devattr);
> @@ -688,7 +705,7 @@ rpcrdma_ep_create(struct rpcrdma_ep *ep, struct rpcrdma_ia *ia,
> ep->rep_attr.cap.max_recv_sge);
>
> /* set trigger for requesting send completion */
> - ep->rep_cqinit = ep->rep_attr.cap.max_send_wr/2 /* - 1*/;
> + ep->rep_cqinit = ep->rep_attr.cap.max_send_wr/2 - 1;
> if (ep->rep_cqinit <= 2)
> ep->rep_cqinit = 0;
> INIT_CQCOUNT(ep);
> @@ -696,26 +713,43 @@ rpcrdma_ep_create(struct rpcrdma_ep *ep, struct rpcrdma_ia *ia,
> init_waitqueue_head(&ep->rep_connect_wait);
> INIT_DELAYED_WORK(&ep->rep_connect_worker, rpcrdma_connect_worker);
>
> - ep->rep_cq = ib_create_cq(ia->ri_id->device, rpcrdma_cq_event_upcall,
> + sendcq = ib_create_cq(ia->ri_id->device, rpcrdma_sendcq_upcall,
> rpcrdma_cq_async_error_upcall, NULL,
> - ep->rep_attr.cap.max_recv_wr +
> ep->rep_attr.cap.max_send_wr + 1, 0);
> - if (IS_ERR(ep->rep_cq)) {
> - rc = PTR_ERR(ep->rep_cq);
> - dprintk("RPC: %s: ib_create_cq failed: %i\n",
> + if (IS_ERR(sendcq)) {
> + rc = PTR_ERR(sendcq);
> + dprintk("RPC: %s: failed to create send CQ: %i\n",
> __func__, rc);
> goto out1;
> }
>
> - rc = ib_req_notify_cq(ep->rep_cq, IB_CQ_NEXT_COMP);
> + rc = ib_req_notify_cq(sendcq, IB_CQ_NEXT_COMP);
> if (rc) {
> dprintk("RPC: %s: ib_req_notify_cq failed: %i\n",
> __func__, rc);
> goto out2;
> }
>
> - ep->rep_attr.send_cq = ep->rep_cq;
> - ep->rep_attr.recv_cq = ep->rep_cq;
> + recvcq = ib_create_cq(ia->ri_id->device, rpcrdma_recvcq_upcall,
> + rpcrdma_cq_async_error_upcall, NULL,
> + ep->rep_attr.cap.max_recv_wr + 1, 0);
> + if (IS_ERR(recvcq)) {
> + rc = PTR_ERR(recvcq);
> + dprintk("RPC: %s: failed to create recv CQ: %i\n",
> + __func__, rc);
> + goto out2;
> + }
> +
> + rc = ib_req_notify_cq(recvcq, IB_CQ_NEXT_COMP);
> + if (rc) {
> + dprintk("RPC: %s: ib_req_notify_cq failed: %i\n",
> + __func__, rc);
> + ib_destroy_cq(recvcq);
> + goto out2;
> + }
> +
> + ep->rep_attr.send_cq = sendcq;
> + ep->rep_attr.recv_cq = recvcq;
>
> /* Initialize cma parameters */
>
> @@ -737,7 +771,7 @@ rpcrdma_ep_create(struct rpcrdma_ep *ep, struct rpcrdma_ia *ia,
> return 0;
>
> out2:
> - err = ib_destroy_cq(ep->rep_cq);
> + err = ib_destroy_cq(sendcq);
> if (err)
> dprintk("RPC: %s: ib_destroy_cq returned %i\n",
> __func__, err);
> @@ -777,8 +811,14 @@ rpcrdma_ep_destroy(struct rpcrdma_ep *ep, struct rpcrdma_ia *ia)
> ep->rep_pad_mr = NULL;
> }
>
> - rpcrdma_clean_cq(ep->rep_cq);
> - rc = ib_destroy_cq(ep->rep_cq);
> + rpcrdma_clean_cq(ep->rep_attr.recv_cq);
> + rc = ib_destroy_cq(ep->rep_attr.recv_cq);
> + if (rc)
> + dprintk("RPC: %s: ib_destroy_cq returned %i\n",
> + __func__, rc);
> +
> + rpcrdma_clean_cq(ep->rep_attr.send_cq);
> + rc = ib_destroy_cq(ep->rep_attr.send_cq);
> if (rc)
> dprintk("RPC: %s: ib_destroy_cq returned %i\n",
> __func__, rc);
> @@ -801,7 +841,9 @@ retry:
> if (rc && rc != -ENOTCONN)
> dprintk("RPC: %s: rpcrdma_ep_disconnect"
> " status %i\n", __func__, rc);
> - rpcrdma_clean_cq(ep->rep_cq);
> +
> + rpcrdma_clean_cq(ep->rep_attr.recv_cq);
> + rpcrdma_clean_cq(ep->rep_attr.send_cq);
>
> xprt = container_of(ia, struct rpcrdma_xprt, rx_ia);
> id = rpcrdma_create_id(xprt, ia,
> @@ -910,7 +952,8 @@ rpcrdma_ep_disconnect(struct rpcrdma_ep *ep, struct rpcrdma_ia *ia)
> {
> int rc;
>
> - rpcrdma_clean_cq(ep->rep_cq);
> + rpcrdma_clean_cq(ep->rep_attr.recv_cq);
> + rpcrdma_clean_cq(ep->rep_attr.send_cq);
> rc = rdma_disconnect(ia->ri_id);
> if (!rc) {
> /* returns without wait if not connected */
> @@ -1793,7 +1836,6 @@ rpcrdma_ep_post_recv(struct rpcrdma_ia *ia,
> ib_dma_sync_single_for_cpu(ia->ri_id->device,
> rep->rr_iov.addr, rep->rr_iov.length, DMA_BIDIRECTIONAL);
>
> - DECR_CQCOUNT(ep);
> rc = ib_post_recv(ia->ri_id->qp, &recv_wr, &recv_wr_fail);
>
> if (rc)
> diff --git a/net/sunrpc/xprtrdma/xprt_rdma.h b/net/sunrpc/xprtrdma/xprt_rdma.h
> index 362a19d..334ab6e 100644
> --- a/net/sunrpc/xprtrdma/xprt_rdma.h
> +++ b/net/sunrpc/xprtrdma/xprt_rdma.h
> @@ -79,7 +79,6 @@ struct rpcrdma_ep {
> int rep_cqinit;
> int rep_connected;
> struct rpcrdma_ia *rep_ia;
> - struct ib_cq *rep_cq;
> struct ib_qp_init_attr rep_attr;
> wait_queue_head_t rep_connect_wait;
> struct ib_sge rep_pad; /* holds zeroed pad */
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-rdma" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-rdma" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
next prev parent reply other threads:[~2014-04-16 12:48 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-04-14 22:22 [PATCH 0/8] NFS/RDMA patches for review Chuck Lever
[not found] ` <20140414220041.20646.63991.stgit-FYjufvaPoItvLzlybtyyYzGyq/o6K9yX@public.gmane.org>
2014-04-14 22:22 ` [PATCH 1/8] xprtrdma: RPC/RDMA must invoke xprt_wake_pending_tasks() in process context Chuck Lever
2014-04-14 22:22 ` [PATCH 2/8] xprtrdma: Remove BOUNCEBUFFERS memory registration mode Chuck Lever
2014-04-14 22:22 ` [PATCH 3/8] xprtrdma: Disable ALLPHYSICAL mode by default Chuck Lever
2014-04-14 22:22 ` [PATCH 4/8] xprtrdma: Remove support for MEMWINDOWS registration mode Chuck Lever
2014-04-14 22:23 ` [PATCH 5/8] xprtrdma: Simplify rpcrdma_deregister_external() synopsis Chuck Lever
2014-04-14 22:23 ` [PATCH 6/8] xprtrdma: Make rpcrdma_ep_destroy() return void Chuck Lever
2014-04-14 22:23 ` [PATCH 7/8] xprtrdma: Split the completion queue Chuck Lever
[not found] ` <20140414222323.20646.66946.stgit-FYjufvaPoItvLzlybtyyYzGyq/o6K9yX@public.gmane.org>
2014-04-16 12:48 ` Sagi Grimberg [this message]
[not found] ` <534E7C1C.5070407-LDSdmyG8hGV8YrgS2mwiifqBs+8SCbDb@public.gmane.org>
2014-04-16 13:30 ` Steve Wise
[not found] ` <534E8608.8030801-7bPotxP6k4+P2YhJcF5u+vpXobYPEAuW@public.gmane.org>
2014-04-16 14:12 ` Sagi Grimberg
[not found] ` <534E8FCE.909-LDSdmyG8hGV8YrgS2mwiifqBs+8SCbDb@public.gmane.org>
2014-04-16 14:25 ` Steve Wise
2014-04-16 14:35 ` Sagi Grimberg
[not found] ` <534E9534.9020004-LDSdmyG8hGV8YrgS2mwiifqBs+8SCbDb@public.gmane.org>
2014-04-16 14:43 ` Steve Wise
2014-04-16 15:18 ` Sagi Grimberg
[not found] ` <534E9F40.8000905-LDSdmyG8hGV8YrgS2mwiifqBs+8SCbDb@public.gmane.org>
2014-04-16 15:46 ` Steve Wise
2014-04-16 15:08 ` Chuck Lever
[not found] ` <E9B601B5-1984-40D8-914A-DDD1380E8183-QHcLZuEGTsvQT0dZR+AlfA@public.gmane.org>
2014-04-16 15:23 ` Sagi Grimberg
[not found] ` <534EA06A.7090200-LDSdmyG8hGV8YrgS2mwiifqBs+8SCbDb@public.gmane.org>
2014-04-16 18:21 ` Chuck Lever
[not found] ` <FC61F219-ACA2-4CE8-BF02-1B8EE2464639-QHcLZuEGTsvQT0dZR+AlfA@public.gmane.org>
2014-04-17 7:06 ` Sagi Grimberg
[not found] ` <534F7D5F.1090908-LDSdmyG8hGV8YrgS2mwiifqBs+8SCbDb@public.gmane.org>
2014-04-17 13:55 ` Chuck Lever
[not found] ` <A7E4B101-BAF0-480C-95AE-26BB845EE2C3-QHcLZuEGTsvQT0dZR+AlfA@public.gmane.org>
2014-04-17 14:34 ` Steve Wise
2014-04-17 19:11 ` Sagi Grimberg
[not found] ` <5350277C.20608-LDSdmyG8hGV8YrgS2mwiifqBs+8SCbDb@public.gmane.org>
2014-04-19 16:31 ` Chuck Lever
[not found] ` <593D9BFA-714E-417F-ACA0-05594290C4D1-QHcLZuEGTsvQT0dZR+AlfA@public.gmane.org>
2014-04-20 12:42 ` Sagi Grimberg
2014-04-17 19:08 ` Sagi Grimberg
2014-04-14 22:23 ` [PATCH 8/8] xprtrdma: Reduce the number of hardway buffer allocations Chuck Lever
2014-04-15 20:15 ` [PATCH 0/8] NFS/RDMA patches for review Steve Wise
[not found] ` <534D9373.3050406-7bPotxP6k4+P2YhJcF5u+vpXobYPEAuW@public.gmane.org>
2014-04-15 20:20 ` Steve Wise
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=534E7C1C.5070407@dev.mellanox.co.il \
--to=sagig-ldsdmyg8hgv8yrgs2mwiifqbs+8scbdb@public.gmane.org \
--cc=chuck.lever-QHcLZuEGTsvQT0dZR+AlfA@public.gmane.org \
--cc=linux-nfs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
--cc=linux-rdma-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox