isn't this addressed by the compiler builtins for atomics?
 -- richard

> On Oct 3, 2019, at 10:38 AM, Matthew Ahrens <mahr...@delphix.com> wrote:
> 
> On Wed, Oct 2, 2019 at 7:51 AM Andriy Gapon <a...@freebsd.org 
> <mailto: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
>  
> <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 <https://openzfs.topicbox.com/latest> / openzfs-developer / see 
> discussions <https://openzfs.topicbox.com/groups/developer> + participants 
> <https://openzfs.topicbox.com/groups/developer/members> + delivery options 
> <https://openzfs.topicbox.com/groups/developer/subscription>Permalink 
> <https://openzfs.topicbox.com/groups/developer/T3ee8a81d5f09f2ec-Mddd0b2abb7554ff63884d196>

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

Reply via email to