tech-kern archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
Re: RFC: localcount_hadref() or localcount_trydarin()
> Date: Mon, 12 Jun 2017 09:09:14 -0400
> From: Thor Lancelot Simon <tls%panix.com@localhost>
>
> On Mon, Jun 12, 2017 at 12:51:29PM +0000, Taylor R Campbell wrote:
> > Why don't you just use a global reference count first? Is the latency
> > and scalability of crypto_newsession and crypto_freesession critical?
>
> For many workloads, it will be, yes.
OK. But we still won't really get any benefit of localcount(9) until
the global locks go away in the first place.
Now suppose
(i) you get rid of those locks;
(ii) you adapt the opencrypto driver lookup to use pserialize(9);
(iii) you still want to allow a kind of `trydetach' operation that
returns EBUSY if there's anything in flight, instead of the `detach'
operation that localcont(9) is currently designed for, which blocks
until the operations are completed; and
(iv) psref(9) won't do it (which may be obvious; I just haven't
thought about using psref(9) for this).
Here's how I would adapt localcount(9) to do this:
1. Unhook the resource so that
(a) new references cannot be acquired (e.g., attempts to look it up
will fail with ENOENT), but
(b) there is still a sentinel in place, so nobody can put another
resource in the same place, because we will need to be able to
put the resource back in place if the operation fails.
You might do this by leaving a pointer to an object in a table, but
setting a flag indicating that the object cannot be used now.
struct driver *drv;
/* detach */
mutex_enter(&drivers.lock);
drv = drivers.table[idx];
if (drv == NULL || drv->flags & DYING)
goto enoent;
drv->flags |= DYING;
...
/* lookup */
struct driver *drv;
pserialize_read_enter();
if ((drv = drivers.table[idx]) == NULL)
goto enoent;
membar_datadep_consumer();
if (drv->flags & DYING)
goto enoent;
localcount_acquire(&drv->localcount);
...
2. Use a new operation localcount_trydrain which does a cross-call to
count up the references and check whether they sum to zero. It is
critical that at this point, new references on any CPU must be
prohibited, so that if the total count changes after this function
answers, it can only go down toward zero.
bool
localcount_trydrain(struct localcount *lc)
{
volatile int64_t total = 0;
KASSERT(lc->lc_totalp == NULL);
/* Count up all the references. */
xc_wait(xc_broadcast(0, drained_xc, lc, __UNVOLATILE(&total)));
/* If there were any, stop here with no permanent effects. */
if (total != 0)
return false;
/*
* All references have drained.
*
* Paranoia: Cause any further use of lc->lc_totalp to crash.
*/
lc->lc_totalp = (void *)(uintptr_t)1;
return true;
}
static void
drained_xc(void *cookie0, void *cookie1)
{
struct localcount *lc = cookie0;
volatile int64_t *totalp = cookie1;
int64_t *localp;
localp = percpu_getref(lc->lc_percpu);
atomic_add_64(totalp, *localp);
percpu_putref(lc->lc_percpu);
}
(This sketch may need to be adapted for 32-bit machines, e.g. to
use a local kmutex_t instead of 64-bit atomics. Exercise for the
reader!)
3. If localcount_trydrain returns false, you must put the resource
back as it was to allow new references again, and stop here.
/* detach */
...
if (!localcount_trydrain(&drv->localcount)) {
drv->flags &= ~DYING;
goto ebusy;
}
4. Otherwise, if localcount_trydrain returns true, then not only are
there no references now, but there can be no new references made
either because you guaranteed that in (1) and (2). Now you can
unhook the sentinel (and perhaps wait for that to be visible on all
CPUs, e.g., with pserialize_perform, if the sentinel uses allocated
memory that you need to free) and destroy the original resource.
/* detach */
...
drivers.table[idx] = NULL;
pserialize_perform(drivers.psz);
pool_put(&drivers.pool, drv);
Both localcount_drain and successful localcount_trydrain are final:
the only thing you can, and must, do with a localcount afterward is
localcount_fini.
Home |
Main Index |
Thread Index |
Old Index