On Wed, Oct 2, 2019 at 7:51 AM Andriy Gapon <a...@freebsd.org> wrote:

>
> This is a work in progress report for my work on safer use of atomics in
> ZFS
> that was discussed in the August developer meeting.
>
> I took the approach suggested at the meeting of creating a new type that
> is to
> be used for all objects that should be modified atomically.
> The type is trivial:
> typedef struct zatomic64 {
>         volatile uint64_t val;
> } zatomic64_t;
>
> I also made a decision upfront to use a wrapper type only for 64-bit
> objects as
> 32-bit objects should be already safe on all supported platforms where a
> native
> integer size is either 32 or 64 bit.
>
> Then, as suggested, I wrote trivial wrappers for all 64-bit atomic
> operations
> that are used in ZFS code.  For example:
>   static inline void
>   zatomic_add_64(zatomic64_t *a, uint64_t val)
>   {
>           atomic_add_64(&a->val, val);
>   }
>
> A side note on naming.  I simply prepended original function names with
> 'z'.
> Although names like zatomic64_add() could be prettier and more aligned
> with the
> type name.
>
> After that I added three new functions:
> - zatomic_load_64 -- safe (with respect to torn values) read of a 64-bit
> atomic
> - zatomic_store_64 -- safe write of a 64-bit atomic
> - zatomic_init_64 -- unsafe write of a 64-bit atomic
>
> The last function just sets zatomic64_t.val and it is supposed to be used
> when
> it is known that there are no concurrent accesses.  It's pretty redundant
> the
> field can be set directly or the whole object can be initialized with x =
> { 0 },
> but I decided to add it for symmetry.
>
> The first two functions are implemented like this:
>   static inline uint64_t
>   zatomic_load_64(zatomic64_t *a)
>   {
>   #ifdef __LP64__
>           return (a->val);
>   #else
>           return (zatomic_add_64_nv(a, 0));
>   #endif
>   }
>
>   static inline void
>   zatomic_store_64(zatomic64_t *a, uint64_t val)
>   {
>   #ifdef __LP64__
>           a->val = val;
>   #else
>           (void)zatomic_swap_64(a, 0);
>   #endif
>   }
>
> I am not sure if this was a right way to do it.
> Maybe there should be a standard implementation only for 64-bit platforms
> and
> each 32-bit platform should do its own thing?
> For example, there is more than one way to get atomic 64-bit loads on x86,
> according to this:
>
> https://stackoverflow.com/questions/48046591/how-do-i-atomically-move-a-64bit-value-in-x86-asm
>
>
What you did above seems fine to me.


> Anyway, I started converting ZFS (/FreeBSD) code to use the new type.
> Immediately, I got some problems and some questions.
>
> First, I am quite hesitant about what to do with kstat-s.
> I wanted to confine the change to ZFS, but kstat is an external thing (at
> least
> on illumos).  For now I decided to leave the kstat-s alone.  Especially as
> I was
> not going to change any code that reads the kstat-s.
> But this also means that some things like arc_c and arc_p, which are
> aliases to
> kstat value, remain potentially unsafe with respect to torn reads.
>

Yeah, we would have to convert these kstats to have callbacks that do the
atomic reads.


>
> I have not converted atomics in the aggsum code yet.
> I am actually a little bit confused about why as_lower_bound and
> as_upper_bound
> are manipulated with atomics.  All manipulations seems to be done under
> as_lock.
> Maybe I overlooked something...
>

The comment in aggsum_flush_bucket() has the (perhaps incorrect)
justification:

/*

* We use atomic instructions for this because we read the upper and

* lower bounds without the lock, so we need stores to be atomic.

*/

atomic_add_64((volatile uint64_t *)&as->as_lower_bound, asb->asc_delta);

So the assumption is that as long as we do an atomic store, we can read
with a normal read.  The lock doesn't help because the readers don't hold
the lock.


>
> I converted refcount.h.
> That had a bit of unexpected effect on rrwlock_t.
> zfs_refcount_t is used for rr_anon_rcount and rr_linked_rcount fields.
> The fields are always accessed rr_lock, so their atomicity is not actually
> required.  I guess that zfs_refcount_t is used for the support of
> references
> with ownership.


That's right.


> So, some bits of the rrwlock code access those fields through
> the refcount interface, but other (conditional) pieces directly access
> rc_count
> as a simple integer.
> So, there are a couple of options here.  The direct accesses can be
> replaced
> with refcount accesses, but then we get unnecessary atomic operations in
> the
> fast paths.


That would be true only on 32-bit platforms, right?  I don't think a small
performance hit to 32-bit kernels is worth worrying about.


> Or the code can keep directly accessing internals of both
> zfs_refcount_t and zatomic64_t.  Another option is to create a copy of
> zfs_refcount_t that relies on external synchronization (so that it does
> not use
> atomics or any internal locks).  But that would be a bit of code
> duplication.
>

I'd prefer to keep the code simple, consistent, and easy to see its
correctness, even if it costs some performance on 32-bit.


>
> Yet another hard case is znode's z_size.
> It's changed with atomic_cas_64() in a single place, in all other places
> it's
> used as a normal integer.  On the one hand, there can be a torn read
> because of
> that atomic operation.  On the other hand, it's quite messy to update all
> uses
> of z_size.
>
> So, the work is progressing, but I feel like limiting the scope of the
> change.
> Originally I planned to convert everything to zatomic in one go.
> But now I feel that just a few things really need that.
> Probably os_obj_next_percpu.
> Likely zfs_refcount_t.
> Anything else?
> I am thinking that maybe a few things could be converted initially and the
> rest
> could be converted on as needed basis.
>
> I don't like having a mix of regular atomics and zatomics, but alas.
>
> Any suggestions, comments, observations, ideas, etc are very welcome!
>
> P.S.
> Another thought is creeping in: maybe it's not worth bothering with ZFS on
> 32-bit platforms.
>

Fine by me, although I think that opinions may vary on this one :-)
 Especially as long as some of the most popular operating systems to run
OpenZFS support 32-bit kernels (although it looks like FreeBSD and Ubuntu
are in the process of dropping support for i386?  But ZoL still supports
much older releases of Ubuntu and RHEL.).

--matt

------------------------------------------
openzfs: openzfs-developer
Permalink: 
https://openzfs.topicbox.com/groups/developer/T3ee8a81d5f09f2ec-Mddd0b2abb7554ff63884d196
Delivery options: https://openzfs.topicbox.com/groups/developer/subscription

Reply via email to