<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <div class="moz-cite-prefix">Am 04.11.21 um 04:40 schrieb Randell
      Jesup:<br>
    </div>
    <blockquote type="cite"
      cite="mid:1d5a8238-fc4f-91cc-d121-e9042f9ff5c3@mozilla.com">
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <div class="moz-cite-prefix">On 10/31/2021 3:36 PM, Aaron Puchert
        wrote:<br>
      </div>
      <blockquote type="cite"
        cite="mid:7dd43b40-0544-64c3-b861-38cfc8dcd754@alice-dsl.net">Thanks
        for letting me know. (I barely follow the list.) <br>
        <blockquote type="cite">
          <blockquote type="cite">On Sat, Oct 30, 2021 at 9:28 PM
            Randell Jesup via cfe-dev <a class="moz-txt-link-rfc2396E"
              href="mailto:cfe-dev@lists.llvm.org"
              moz-do-not-send="true"><cfe-dev@lists.llvm.org></a>
            wrote: <br>
            <blockquote type="cite">One way to possibly handle the
              reader/writer vs readers case (where reads on the writing
              thread don't need to lock) would be to be able to say
              "guarded by this or that", in this case something like
              GUARDED_BY(mMutex, MainThread) (GUARDED_BY(mMutex ||
              MainThread) ??). <br>
            </blockquote>
          </blockquote>
        </blockquote>
        <br>
        There has been some work on logical expressions in capability
        attributes in <a class="moz-txt-link-freetext"
href="https://reviews.llvm.org/rG7c192b452fa2b3c63ed547e0ff88a5e62765b59f"
          moz-do-not-send="true">https://reviews.llvm.org/rG7c192b452fa2b3c63ed547e0ff88a5e62765b59f</a>,
        but I believe it's not functional yet. I thought about the
        semantics of this a bit, but don't have a good understanding
        yet. <br>
        <br>
        If you want accesses to a resource to be protected, it's in
        general not sufficient to have one of a set of capabilities.
        There can only be one. What happens in this scenario is that
        there is a certain period of time where the resource is
        exclusive to the main thread, and another where it's only
        accessible via mutex (even if the main thread were to access
        it). So maybe logical expressions aren't the right way to
        understand this situation, but rather there should be different
        types, one that has GUARDED_BY(MainThread) and another that has
        GUARDED_BY(mMutex), and at some point we convert between them.
        In some sense the protection regime changes throughout the
        lifetime, and for static analysis that means we need a new
        (static) type. <br>
      </blockquote>
      <p><br>
      </p>
      <p>Well, the example I gave doesn't match that, I believe.  This
        is a value only written on Thread 1, and read on Thread 1 and on
        other threads. The requirements for locking would be the lock
        around all writes (on thread 1), and lock around all reads from
        threads *other* than thread 1.  Reads from Thread 1 don't need
        to lock, since they're on the only thread that ever writes to
        the value.   This is conceptually GUARDED_BY(mMutex ||
        OnThread1).<br>
      </p>
      <p>The case you're referencing sounds more like "value is freely
        written and read on thread 1, then at some point later access to
        it is provided to other threads, and after that happens access
        is controlled by a mutex" - where the protection regime changes
        over time (and may also correlate with specific methods, like an
        Init() method).<br>
      </p>
    </blockquote>
    <p>You're absolutely right, I was misreading your case.</p>
    <p>What you're describing sounds in fact like thread 1 always has a
      shared lock, which it sometimes promotes to exclusive lock, then
      demotes to a shared lock again. The other threads can only acquire
      shared locks, because thread 1 doesn't ever release its shared
      lock. Now there are multiple ways to write that down:</p>
    <ul>
      <li>Use an actual “multiple readers / single writer” lock, also
        known as read/write lock. Thread 1 never releases it. Then we
        can always read, and we block other threads from ever acquiring
        a write lock. They can still get a read lock though if we don't
        have the write lock.</li>
      <li>If that's not desirable, for example because multiple readers
        aren't needed, or a read/write lock would be overkill, build a
        “fake read/write lock” that looks like a read/write lock to the
        thread safety analysis, but is actually implemented via mutex.
        It would have different entry points for the different threads:</li>
      <ul>
        <li>LockMain acquires the mutex and is annotated
          RELEASE_SHARED() ACQUIRE(), perhaps REQUIRES(main).<br>
        </li>
        <li>UnlockMain releases the mutex and is annotated RELEASE()
          ACQUIRE_SHARED(), perhaps REQUIRES(main).<br>
        </li>
        <li>LockOther acquires the mutex and is annotated
          ACQUIRE_SHARED().</li>
        <li>UnlockOther releases the mutex and is annotated
          RELEASE_SHARED().</li>
      </ul>
    </ul>
    <p>There might be more options, but you get the idea. Conceptually
      you don't have multiple capabilities, you have just one. The key
      is which modes (shared/exclusive) they are held in, and who can
      acquire that capability in which mode.</p>
    <p>The problem with booleans is that they mess with exclusivity:
      mMutex || OnThread1 seems to imply that either of them is fine.
      But the other threads can't get write access even if they have the
      mutex. In fact the mutex gives different threads different levels
      of access. That's why I think you actually have a (possibly
      specialized) read/write lock here. And note that the second option
      should be a zero-cost abstraction around a mutex.<br>
    </p>
    <blockquote type="cite"
      cite="mid:1d5a8238-fc4f-91cc-d121-e9042f9ff5c3@mozilla.com">
      <p> </p>
      <p>We have another pattern that's interesting, where a method may
        use Maybe<MutexAutoLock> lock(std::in_place, mMutex); 
        (Maybe<> is effectively std::optional).   This allows us
        to lock.reset() instead of using MutexAutoUnlock lock(mLock),
        which will lead to an extra lock/unlock pair compared to
        Maybe<> and lock.reset().</p>
    </blockquote>
    <p>Supporting that would be hard, because we'd need to look into the
      Maybe type, and then the analysis isn't local anymore. However,
      the analysis supports “premature unlocking” of scopes, i.e. you
      can write lock.Unlock(). I've even added support for “relockable
      scopes”, where you can lock.Lock() again. (Have a look at the
      MutexLocker in
      <a class="moz-txt-link-freetext" href="https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#mutex-h">https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#mutex-h</a> to
      see what is possible.)</p>
    <p>The philosophy that we follow with scope types is that they
      should work as local variables whose lifetime is determined by a
      scope. Anything else is “out of scope”, which includes types like
      optional that invoke the destructor manually. Also the scopes (at
      least for now) need to be bound to a fixed set of mutexes for
      their entire lifetime.<br>
    </p>
    <blockquote type="cite"
      cite="mid:1d5a8238-fc4f-91cc-d121-e9042f9ff5c3@mozilla.com">
      <p>We also have an RAII MutexAutoTryLock class, which is like
        MutexAutoLock but does a trylock operation, and also you can
        convert it to bool to see if it obtained the lock.   I don't
        think this is easily representable in the current system.  (I'll
        note that we use it 4 times in our codebase, and 3 of those are
        in Mutext test code, so I don't care that much ;-) .)<br>
      </p>
    </blockquote>
    <p>Same problem. We support a scope with deferred locking and then
      try-acquiring that. To stay in the language of mutex.h:</p>
    <p>MutexLocker lock(&mu, defer_lock);<br>
      if (lock.TryLock())<br>
          ...</p>
    <p>If the try-acquire is hidden in the constructor, and we obtain
      the value only later, that seems hard to support. (For starters,
      we'd need additional attributes because TRY_ACQUIRE wants a return
      value.)</p>
    <p>If you're worried about scope, you can with C++17 write<br>
    </p>
    <p>
      if (MutexLocker lock(&mu, defer_lock); lock.TryLock())<br>
          ...</p>
    <p>which I guess looks almost like what you can do with
      MutexAutoTryLock.</p>
    <p>Aaron<br>
    </p>
  </body>
</html>