From: Olivier Matz <olivier.matz@6wind.com>
To: David Marchand <david.marchand@redhat.com>
Cc: dev <dev@dpdk.org>, Jerin Jacob <jerinjacobk@gmail.com>,
Bruce Richardson <bruce.richardson@intel.com>,
Ray Kinsella <mdr@ashroe.eu>,
Thomas Monjalon <thomas@monjalon.net>,
Andrew Rybchenko <arybchenko@solarflare.com>,
Kevin Traynor <ktraynor@redhat.com>,
Ian Stokes <ian.stokes@intel.com>,
Ilya Maximets <i.maximets@ovn.org>,
John McNamara <john.mcnamara@intel.com>,
Marko Kovacevic <marko.kovacevic@intel.com>,
Anatoly Burakov <anatoly.burakov@intel.com>,
Neil Horman <nhorman@tuxdriver.com>
Subject: Re: [dpdk-dev] [PATCH v4 6/9] eal: register non-EAL threads as lcores
Date: Wed, 1 Jul 2020 11:11:05 +0200 [thread overview]
Message-ID: <20200701091105.GO5869@platinum> (raw)
In-Reply-To: <CAJFAV8xGT-4-Yy672d8GmOrNxHpJJHXhzZ=VRNt0jFpNmgJjmA@mail.gmail.com>
On Wed, Jul 01, 2020 at 09:13:36AM +0200, David Marchand wrote:
> On Tue, Jun 30, 2020 at 12:07 PM Olivier Matz <olivier.matz@6wind.com> wrote:
> >
> > On Fri, Jun 26, 2020 at 04:47:33PM +0200, David Marchand wrote:
> > > DPDK allows calling some part of its API from a non-EAL thread but this
> > > has some limitations.
> > > OVS (and other applications) has its own thread management but still
> > > want to avoid such limitations by hacking RTE_PER_LCORE(_lcore_id) and
> > > faking EAL threads potentially unknown of some DPDK component.
> > >
> > > Introduce a new API to register non-EAL thread and associate them to a
> > > free lcore with a new NON_EAL role.
> > > This role denotes lcores that do not run DPDK mainloop and as such
> > > prevents use of rte_eal_wait_lcore() and consorts.
> > >
> > > Multiprocess is not supported as the need for cohabitation with this new
> > > feature is unclear at the moment.
> > >
> > > Signed-off-by: David Marchand <david.marchand@redhat.com>
> > > Acked-by: Andrew Rybchenko <arybchenko@solarflare.com>
> > > ---
> > > Changes since v2:
> > > - refused multiprocess init once rte_thread_register got called, and
> > > vice versa,
> > > - added warning on multiprocess in rte_thread_register doxygen,
> > >
> > > Changes since v1:
> > > - moved cleanup on lcore role code in patch 5,
> > > - added unit test,
> > > - updated documentation,
> > > - changed naming from "external thread" to "registered non-EAL thread"
> > >
> > > ---
> > > MAINTAINERS | 1 +
> > > app/test/Makefile | 1 +
> > > app/test/autotest_data.py | 6 +
> > > app/test/meson.build | 2 +
> > > app/test/test_lcores.c | 139 ++++++++++++++++++
> > > doc/guides/howto/debug_troubleshoot.rst | 5 +-
> > > .../prog_guide/env_abstraction_layer.rst | 22 +--
> > > doc/guides/prog_guide/mempool_lib.rst | 2 +-
> > > lib/librte_eal/common/eal_common_lcore.c | 50 ++++++-
> > > lib/librte_eal/common/eal_common_mcfg.c | 36 +++++
> > > lib/librte_eal/common/eal_common_thread.c | 33 +++++
> > > lib/librte_eal/common/eal_memcfg.h | 10 ++
> > > lib/librte_eal/common/eal_private.h | 18 +++
> > > lib/librte_eal/freebsd/eal.c | 4 +
> > > lib/librte_eal/include/rte_lcore.h | 25 +++-
> > > lib/librte_eal/linux/eal.c | 4 +
> > > lib/librte_eal/rte_eal_version.map | 2 +
> > > lib/librte_mempool/rte_mempool.h | 11 +-
> > > 18 files changed, 349 insertions(+), 22 deletions(-)
> > > create mode 100644 app/test/test_lcores.c
> > >
> >
> > [...]
> >
> > > diff --git a/app/test/test_lcores.c b/app/test/test_lcores.c
> > > new file mode 100644
> > > index 0000000000..864bcbade7
> > > --- /dev/null
> > > +++ b/app/test/test_lcores.c
> > > @@ -0,0 +1,139 @@
> > > +/* SPDX-License-Identifier: BSD-3-Clause
> > > + * Copyright (c) 2020 Red Hat, Inc.
> > > + */
> > > +
> > > +#include <pthread.h>
> > > +#include <string.h>
> > > +
> > > +#include <rte_lcore.h>
> > > +
> > > +#include "test.h"
> > > +
> > > +struct thread_context {
> > > + enum { INIT, ERROR, DONE } state;
> > > + bool lcore_id_any;
> > > + pthread_t id;
> > > + unsigned int *registered_count;
> > > +};
> > > +static void *thread_loop(void *arg)
> > > +{
> >
> > missing an empty line here
> >
> > > + struct thread_context *t = arg;
> > > + unsigned int lcore_id;
> > > +
> > > + lcore_id = rte_lcore_id();
> > > + if (lcore_id != LCORE_ID_ANY) {
> > > + printf("Incorrect lcore id for new thread %u\n", lcore_id);
> > > + t->state = ERROR;
> > > + }
> > > + rte_thread_register();
> > > + lcore_id = rte_lcore_id();
> > > + if ((t->lcore_id_any && lcore_id != LCORE_ID_ANY) ||
> > > + (!t->lcore_id_any && lcore_id == LCORE_ID_ANY)) {
> > > + printf("Could not register new thread, got %u while %sexpecting %u\n",
> > > + lcore_id, t->lcore_id_any ? "" : "not ", LCORE_ID_ANY);
> > > + t->state = ERROR;
> > > + }
> >
> > To check if rte_thread_register() succedeed, we need to look at
> > lcore_id. I wonder if rte_thread_register() shouldn't return the lcore
> > id on success, and -1 on error (rte_errno could be set to give some
> > info on the error).
>
> lcore_id are unsigned integers with the special value LCORE_ID_ANY
> mapped to UINT32_MAX (should be UINT_MAX? anyway...).
>
> rte_thread_register could return an error code as there are no ERROR
> level logs about why a lcore allocation failed.
> We could then distinguish a shortage of lcore (or init callback
> refusal) from an invalid call before rte_eal_init() or when mp is in
> use.
>
> About returning the lcore_id as part of the return code, this would
> map to -1 for LCORE_ID_ANY.
> This is probably not a problem but still odd.
Yes, it would be a bit odd like this. What about changing the definition of
LCORE_ID_ANY to ((unsigned int)-1) ? I think it does not change the effective
value on any architecture, but would make the above change clearer.
> >
> > The same could be done for rte_thread_init()
>
> ?
> Not sure where this one could fail.
I was thinking about __rte_trace_mem_per_thread_alloc(), but maybe it's not
needed.
> >
> > [...]
> >
> > > diff --git a/lib/librte_eal/common/eal_common_thread.c b/lib/librte_eal/common/eal_common_thread.c
> > > index a7ae0691bf..1cbddc4b5b 100644
> > > --- a/lib/librte_eal/common/eal_common_thread.c
> > > +++ b/lib/librte_eal/common/eal_common_thread.c
> > > @@ -236,3 +236,36 @@ rte_ctrl_thread_create(pthread_t *thread, const char *name,
> > > pthread_join(*thread, NULL);
> > > return -ret;
> > > }
> > > +
> > > +void
> > > +rte_thread_register(void)
> > > +{
> > > + unsigned int lcore_id;
> > > + rte_cpuset_t cpuset;
> > > +
> > > + /* EAL init flushes all lcores, we can't register before. */
> > > + assert(internal_config.init_complete == 1);
> > > + if (pthread_getaffinity_np(pthread_self(), sizeof(cpuset),
> > > + &cpuset) != 0)
> > > + CPU_ZERO(&cpuset);
> > > + lcore_id = eal_lcore_non_eal_allocate();
> > > + if (lcore_id >= RTE_MAX_LCORE)
> > > + lcore_id = LCORE_ID_ANY;
> > > + rte_thread_init(lcore_id, &cpuset);
> > > + if (lcore_id != LCORE_ID_ANY)
> > > + RTE_LOG(DEBUG, EAL, "Registered non-EAL thread as lcore %u.\n",
> > > + lcore_id);
> > > +}
> >
> > So, in this case, the affinity of the pthread is kept and saved, in other
> > words there is no link between the lcore id and the affinity. It means we
> > are allowing an application to register lcores for dataplane with conflicting
> > affinities.
>
> This is not something new, applications using --lcores option already
> live with this.
> We have warnings in the documentation about non-EAL threads and about
> the dangers of conflicting affinities.
> Hopefully, the users of this API know what they are doing since they
> chose not to use EAL threads.
>
>
> >
> > I wonder if it could be useful to have an API that automatically sets
> > the affinity according to the lcore_id. Or a function that creates a
> > pthread using the specified lcore id, and setting the correct affinity.
> > I could simplify the work for applications that want to create/destroy
> > dataplane threads dynamically.
>
> Do you mean EAL threads dynamic creation/suppression?
>
>
> >
> > This could be done later however, just an idea.
>
> For now, I don't see the need.
>
>
> >
> > [...]
> > > diff --git a/lib/librte_eal/freebsd/eal.c b/lib/librte_eal/freebsd/eal.c
> > > index 13e5de006f..32a3d999b8 100644
> > > --- a/lib/librte_eal/freebsd/eal.c
> > > +++ b/lib/librte_eal/freebsd/eal.c
> > > @@ -424,6 +424,10 @@ rte_config_init(void)
> > > }
> > > if (rte_eal_config_reattach() < 0)
> > > return -1;
> > > + if (!eal_mcfg_enable_multiprocess()) {
> > > + RTE_LOG(ERR, EAL, "Primary process refused secondary attachment\n");
> > > + return -1;
> > > + }
> > > eal_mcfg_update_internal();
> > > break;
> > > case RTE_PROC_AUTO:
> > > diff --git a/lib/librte_eal/include/rte_lcore.h b/lib/librte_eal/include/rte_lcore.h
> > > index 3968c40693..43747e88df 100644
> > > --- a/lib/librte_eal/include/rte_lcore.h
> > > +++ b/lib/librte_eal/include/rte_lcore.h
> > > @@ -31,6 +31,7 @@ enum rte_lcore_role_t {
> > > ROLE_RTE,
> > > ROLE_OFF,
> > > ROLE_SERVICE,
> > > + ROLE_NON_EAL,
> > > };
> >
> > If find the name ROLE_NON_EAL a bit heavy (this was also my impression
> > when reading the doc part).
> >
> > I understand that there are several types of threads:
> >
> > - eal (pthread created by eal): ROLE_RTE and ROLE_SERVICE
> > - unregistered (pthread not created by eal, and not registered): ROLE_OFF
> > (note that ROLE_OFF also applies for unexistant threads)
> > - dynamic: pthread not created by eal, but registered
>
> Last two cases both are non-EAL threads as described in the doc so far.
Yes, but only the last case has the NON_EAL role.
I feel that currently role != thread_type, so may be a bit confusing to
use a pthread_type name in the role enum.
>
>
> >
> > What about using ROLE_DYN ? I'm not sure about this name either, it's just
> > to open the discussion :)
> >
>
> Well, at the moment, all those new lcores are mapped only to non-EAL threads.
> A dynamic role feels like you want to take dynamic EAL threads into
> account from the start.
> I prefer to stick to non-EAL.
>
>
> --
> David Marchand
>
next prev parent reply other threads:[~2020-07-01 9:11 UTC|newest]
Thread overview: 133+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-06-10 14:44 [dpdk-dev] [PATCH 0/7] Register external threads as lcore David Marchand
2020-06-10 14:45 ` [dpdk-dev] [PATCH 1/7] eal: relocate per thread symbols to common David Marchand
2020-06-10 14:45 ` [dpdk-dev] [PATCH 2/7] eal: fix multiple definition of per lcore thread id David Marchand
2020-06-15 6:46 ` Kinsella, Ray
2020-06-10 14:45 ` [dpdk-dev] [PATCH 3/7] eal: introduce thread init helper David Marchand
2020-06-10 14:45 ` [dpdk-dev] [PATCH 4/7] eal: introduce thread uninit helper David Marchand
2020-06-10 14:45 ` [dpdk-dev] [PATCH 5/7] eal: register non-EAL threads as lcore David Marchand
2020-06-15 6:43 ` Kinsella, Ray
2020-06-10 14:45 ` [dpdk-dev] [PATCH 6/7] eal: dump lcores David Marchand
2020-06-15 6:40 ` Kinsella, Ray
2020-06-10 14:45 ` [dpdk-dev] [PATCH 7/7] eal: add lcore hotplug notifications David Marchand
2020-06-15 6:34 ` Kinsella, Ray
2020-06-15 7:13 ` David Marchand
2020-06-10 15:09 ` [dpdk-dev] [PATCH 0/7] Register external threads as lcore Jerin Jacob
2020-06-10 15:13 ` Bruce Richardson
2020-06-10 15:18 ` David Marchand
2020-06-10 15:33 ` Jerin Jacob
2020-06-15 7:11 ` David Marchand
2020-06-19 16:22 ` [dpdk-dev] [PATCH v2 0/9] Register non-EAL " David Marchand
2020-06-19 16:22 ` [dpdk-dev] [PATCH v2 1/9] eal: relocate per thread symbols to common David Marchand
2020-06-19 16:22 ` [dpdk-dev] [PATCH v2 2/9] eal: fix multiple definition of per lcore thread id David Marchand
2020-06-19 16:22 ` [dpdk-dev] [PATCH v2 3/9] eal: introduce thread init helper David Marchand
2020-06-19 16:22 ` [dpdk-dev] [PATCH v2 4/9] eal: introduce thread uninit helper David Marchand
2020-06-19 16:22 ` [dpdk-dev] [PATCH v2 5/9] eal: move lcore role code David Marchand
2020-06-19 16:22 ` [dpdk-dev] [PATCH v2 6/9] eal: register non-EAL threads as lcores David Marchand
2020-06-19 16:22 ` [dpdk-dev] [PATCH v2 7/9] eal: add lcore init callbacks David Marchand
2020-06-19 16:22 ` [dpdk-dev] [PATCH v2 8/9] eal: add lcore iterators David Marchand
2020-06-20 2:21 ` Stephen Hemminger
2020-06-19 16:22 ` [dpdk-dev] [PATCH v2 9/9] mempool/bucket: handle non-EAL lcores David Marchand
2020-06-22 13:25 ` [dpdk-dev] [PATCH v3 0/9] Register non-EAL threads as lcore David Marchand
2020-06-22 13:25 ` [dpdk-dev] [PATCH v3 1/9] eal: relocate per thread symbols to common David Marchand
2020-06-22 13:25 ` [dpdk-dev] [PATCH v3 2/9] eal: fix multiple definition of per lcore thread id David Marchand
2020-06-22 13:25 ` [dpdk-dev] [PATCH v3 3/9] eal: introduce thread init helper David Marchand
2020-06-22 13:25 ` [dpdk-dev] [PATCH v3 4/9] eal: introduce thread uninit helper David Marchand
2020-06-22 13:25 ` [dpdk-dev] [PATCH v3 5/9] eal: move lcore role code David Marchand
2020-06-22 13:25 ` [dpdk-dev] [PATCH v3 6/9] eal: register non-EAL threads as lcores David Marchand
2020-06-22 15:49 ` Ananyev, Konstantin
2020-06-22 16:37 ` Ananyev, Konstantin
2020-06-23 7:49 ` David Marchand
2020-06-23 9:14 ` Bruce Richardson
2020-06-23 12:49 ` David Marchand
2020-06-23 13:15 ` Ananyev, Konstantin
2020-06-24 9:23 ` David Marchand
2020-06-24 9:56 ` Bruce Richardson
2020-06-24 10:08 ` Thomas Monjalon
2020-06-24 10:45 ` Ananyev, Konstantin
2020-06-24 10:39 ` Ananyev, Konstantin
2020-06-24 10:48 ` David Marchand
2020-06-24 11:59 ` Ananyev, Konstantin
2020-06-26 14:43 ` David Marchand
2020-06-30 10:35 ` Thomas Monjalon
2020-06-30 12:07 ` Ananyev, Konstantin
2020-06-30 12:44 ` Olivier Matz
2020-06-30 14:37 ` Thomas Monjalon
2020-06-30 19:02 ` Ananyev, Konstantin
2020-06-30 14:35 ` Thomas Monjalon
2020-06-30 18:57 ` Ananyev, Konstantin
2020-07-01 7:48 ` David Marchand
2020-07-01 11:58 ` Ananyev, Konstantin
2020-07-02 13:06 ` David Marchand
2020-07-03 15:15 ` Thomas Monjalon
2020-07-03 16:40 ` Ananyev, Konstantin
2020-07-04 15:00 ` David Marchand
2020-07-04 21:24 ` Ananyev, Konstantin
2020-06-23 17:02 ` Andrew Rybchenko
2020-06-22 13:25 ` [dpdk-dev] [PATCH v3 7/9] eal: add lcore init callbacks David Marchand
2020-06-22 13:25 ` [dpdk-dev] [PATCH v3 8/9] eal: add lcore iterators David Marchand
2020-06-22 13:25 ` [dpdk-dev] [PATCH v3 9/9] mempool/bucket: handle non-EAL lcores David Marchand
2020-06-23 17:28 ` Andrew Rybchenko
2020-06-26 14:13 ` David Marchand
2020-06-26 14:34 ` Andrew Rybchenko
2020-06-26 14:47 ` [dpdk-dev] [PATCH v4 0/9] Register non-EAL threads as lcore David Marchand
2020-06-26 14:47 ` [dpdk-dev] [PATCH v4 1/9] eal: relocate per thread symbols to common David Marchand
2020-06-30 9:33 ` Olivier Matz
2020-06-26 14:47 ` [dpdk-dev] [PATCH v4 2/9] eal: fix multiple definition of per lcore thread id David Marchand
2020-06-30 9:34 ` Olivier Matz
2020-06-26 14:47 ` [dpdk-dev] [PATCH v4 3/9] eal: introduce thread init helper David Marchand
2020-06-30 9:37 ` Olivier Matz
2020-06-30 12:04 ` David Marchand
2020-06-26 14:47 ` [dpdk-dev] [PATCH v4 4/9] eal: introduce thread uninit helper David Marchand
2020-06-26 15:00 ` Jerin Jacob
2020-06-29 9:07 ` David Marchand
2020-06-29 8:59 ` [dpdk-dev] [EXT] " Sunil Kumar Kori
2020-06-29 9:25 ` David Marchand
2020-06-30 9:42 ` [dpdk-dev] " Olivier Matz
2020-07-01 8:00 ` David Marchand
2020-06-26 14:47 ` [dpdk-dev] [PATCH v4 5/9] eal: move lcore role code David Marchand
2020-06-30 9:45 ` Olivier Matz
2020-06-26 14:47 ` [dpdk-dev] [PATCH v4 6/9] eal: register non-EAL threads as lcores David Marchand
2020-06-29 14:27 ` Ananyev, Konstantin
2020-06-30 10:07 ` Olivier Matz
2020-07-01 7:13 ` David Marchand
2020-07-01 9:11 ` Olivier Matz [this message]
2020-06-26 14:47 ` [dpdk-dev] [PATCH v4 7/9] eal: add lcore init callbacks David Marchand
2020-06-29 12:46 ` Ananyev, Konstantin
2020-06-30 10:09 ` Olivier Matz
2020-06-30 10:15 ` Olivier Matz
2020-06-26 14:47 ` [dpdk-dev] [PATCH v4 8/9] eal: add lcore iterators David Marchand
2020-06-30 10:11 ` Olivier Matz
2020-06-26 14:47 ` [dpdk-dev] [PATCH v4 9/9] mempool/bucket: handle non-EAL lcores David Marchand
2020-06-26 14:52 ` Andrew Rybchenko
2020-07-06 14:15 ` [dpdk-dev] [PATCH v5 00/10] Register non-EAL threads as lcore David Marchand
2020-07-06 14:15 ` [dpdk-dev] [PATCH v5 01/10] eal: relocate per thread symbols to common David Marchand
2020-07-06 14:15 ` [dpdk-dev] [PATCH v5 02/10] eal: fix multiple definition of per lcore thread id David Marchand
2020-07-06 14:15 ` [dpdk-dev] [PATCH v5 03/10] eal: introduce thread init helper David Marchand
2020-07-06 14:16 ` [dpdk-dev] [PATCH v5 04/10] eal: introduce thread uninit helper David Marchand
2020-07-06 14:16 ` [dpdk-dev] [PATCH v5 05/10] eal: move lcore role code David Marchand
2020-07-06 14:16 ` [dpdk-dev] [PATCH v5 06/10] eal: register non-EAL threads as lcores David Marchand
2020-07-06 14:16 ` [dpdk-dev] [PATCH v5 07/10] eal: add lcore init callbacks David Marchand
2020-07-06 14:16 ` [dpdk-dev] [PATCH v5 08/10] eal: add lcore iterators David Marchand
2020-07-06 14:16 ` [dpdk-dev] [PATCH v5 09/10] mempool/bucket: handle non-EAL lcores David Marchand
2020-07-06 14:16 ` [dpdk-dev] [PATCH v5 10/10] eal: add multiprocess disable API David Marchand
2020-07-06 20:52 ` [dpdk-dev] [PATCH v6 00/10] Register non-EAL threads as lcore David Marchand
2020-07-06 20:52 ` [dpdk-dev] [PATCH v6 01/10] eal: relocate per thread symbols to common David Marchand
2020-07-06 20:52 ` [dpdk-dev] [PATCH v6 02/10] eal: fix multiple definition of per lcore thread id David Marchand
2020-07-06 20:52 ` [dpdk-dev] [PATCH v6 03/10] eal: introduce thread init helper David Marchand
2020-07-06 20:52 ` [dpdk-dev] [PATCH v6 04/10] eal: introduce thread uninit helper David Marchand
2020-07-06 20:52 ` [dpdk-dev] [PATCH v6 05/10] eal: move lcore role code David Marchand
2020-07-06 20:52 ` [dpdk-dev] [PATCH v6 06/10] eal: register non-EAL threads as lcores David Marchand
2022-11-03 9:02 ` Morten Brørup
2022-11-03 10:15 ` Thomas Monjalon
2020-07-06 20:52 ` [dpdk-dev] [PATCH v6 07/10] eal: add lcore init callbacks David Marchand
2022-12-15 9:05 ` Morten Brørup
2022-12-15 9:09 ` David Marchand
2022-12-15 10:21 ` Morten Brørup
2022-12-16 8:09 ` David Marchand
2022-12-16 9:55 ` Morten Brørup
2020-07-06 20:52 ` [dpdk-dev] [PATCH v6 08/10] eal: add lcore iterators David Marchand
2020-07-06 20:52 ` [dpdk-dev] [PATCH v6 09/10] mempool/bucket: handle non-EAL lcores David Marchand
2020-07-06 20:52 ` [dpdk-dev] [PATCH v6 10/10] eal: add multiprocess disable API David Marchand
2020-07-06 23:22 ` [dpdk-dev] [PATCH v6 00/10] Register non-EAL threads as lcore Ananyev, Konstantin
2020-07-08 13:05 ` David Marchand
2020-07-08 13:06 ` David Marchand
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=20200701091105.GO5869@platinum \
--to=olivier.matz@6wind.com \
--cc=anatoly.burakov@intel.com \
--cc=arybchenko@solarflare.com \
--cc=bruce.richardson@intel.com \
--cc=david.marchand@redhat.com \
--cc=dev@dpdk.org \
--cc=i.maximets@ovn.org \
--cc=ian.stokes@intel.com \
--cc=jerinjacobk@gmail.com \
--cc=john.mcnamara@intel.com \
--cc=ktraynor@redhat.com \
--cc=marko.kovacevic@intel.com \
--cc=mdr@ashroe.eu \
--cc=nhorman@tuxdriver.com \
--cc=thomas@monjalon.net \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.