¿Dónde está la carrera en este hilo de advertencia de desinfectante?

8

El siguiente código genera una advertencia cuando se ejecuta con el desinfectante de hilos en macOS. No puedo ver dónde está la carrera. El bloque de control de shared_ptr y weak_ptr es seguro para subprocesos, y al empujar y hacer estallar std::queuese realiza con un bloqueo retenido.

#include <future>
#include <memory>
#include <queue>

class Foo {
public:
  Foo() {
    fut = std::async(std::launch::async, [this] {
      while (!shouldStop) {
        std::scoped_lock lock(mut);
        while (!requests.empty()) {
          std::weak_ptr<float> requestData = requests.front();
          requests.pop();
          (void)requestData;
        }
      }
    });
  }

  ~Foo() {
    shouldStop.store(true);
    fut.get();
  }

  void add(const std::weak_ptr<float> subscriber) {
    std::scoped_lock lock(mut);
    requests.push(subscriber);
  }

private:
  std::atomic<bool> shouldStop = false;
  std::future<void> fut;
  std::queue<std::weak_ptr<float>> requests;
  std::mutex mut;
};

int main() {
  Foo foo;

  int numIterations = 100000;

  while (--numIterations) {
    auto subscriber = std::make_shared<float>();

    foo.add(subscriber);
    subscriber.reset();
  }

  std::this_thread::sleep_for(std::chrono::seconds(1));
}

Advertencia con stacktrace:

WARNING: ThreadSanitizer: data race (pid=11176)
  Write of size 8 at 0x7b0800000368 by thread T1 (mutexes: write M16):
    #0 operator delete(void*) <null>:1062032 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x4f225)
    #1 std::__1::__shared_ptr_emplace<float, std::__1::allocator<float> >::__on_zero_shared_weak() new:272 (minimal:x86_64+0x1000113de)
    #2 std::__1::weak_ptr<float>::~weak_ptr() memory:5148 (minimal:x86_64+0x100010762)
    #3 std::__1::weak_ptr<float>::~weak_ptr() memory:5146 (minimal:x86_64+0x100002448)
    #4 Foo::Foo()::'lambda'()::operator()() const minimal_race.cpp:15 (minimal:x86_64+0x10000576e)
    #5 void std::__1::__async_func<Foo::Foo()::'lambda'()>::__execute<>(std::__1::__tuple_indices<>) type_traits:4345 (minimal:x86_64+0x1000052f0)
    #6 std::__1::__async_func<Foo::Foo()::'lambda'()>::operator()() future:2323 (minimal:x86_64+0x100005268)
    #7 std::__1::__async_assoc_state<void, std::__1::__async_func<Foo::Foo()::'lambda'()> >::__execute() future:1040 (minimal:x86_64+0x100005119)
    #8 void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, void (std::__1::__async_assoc_state<void, std::__1::__async_func<Foo::Foo()::'lambda'()> >::*)(), std::__1::__async_assoc_state<void, std::__1::__async_func<Foo::Foo()::'lambda'()> >*> >(void*) type_traits:4286 (minimal:x86_64+0x10000717c)

  Previous atomic write of size 8 at 0x7b0800000368 by main thread:
    #0 __tsan_atomic64_fetch_add <null>:1062032 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x24cdd)
    #1 std::__1::shared_ptr<float>::~shared_ptr() memory:3472 (minimal:x86_64+0x1000114d4)
    #2 std::__1::shared_ptr<float>::~shared_ptr() memory:4502 (minimal:x86_64+0x100002488)
    #3 main memory:4639 (minimal:x86_64+0x10000210b)

SUMMARY: ThreadSanitizer: data race new:272 in std::__1::__shared_ptr_emplace<float, std::__1::allocator<float> >::__on_zero_shared_weak()

editar: lo compilo con:

clang++ -std=c++17 -g -fsanitize=thread -o test  minimal_race.cpp

versión clang:

$ clang++ --version
clang version 7.1.0 (tags/RELEASE_710/final)
Target: x86_64-apple-darwin18.7.0
Thread model: posix
InstalledDir: /usr/local/opt/llvm@7/bin

Estoy usando macOS 10.14.6

tuple_cat
fuente
Creo que esto pertenece a codereview
xception
2
No, está bien aquí. Hay un problema con el código de OP, no solo buscan mejorarlo. Una condición de carrera es un problema muy grave
Tas
probado un par de veces con desinfectante hilo con gcc y sonido metálico en Linux y no se pudo reproducir el problema
Xception
¿No causaría esto un posible punto muerto? Con std::launch::async, depende de std::asyncdeterminar cómo programar sus solicitudes de acuerdo con cppreference.com . Esto significa que potencialmente cuando Foose construye, el futurecerraduras el mutex (que no lo hace de desbloqueo hasta que shouldStopes verdadero). Si Foo::Addluego se llama, intentará bloquear el mutex, esperando que el futuro lo desbloquee, lo que nunca hace.
xEric_xD
1
logró obtener un mejor seguimiento con libc ++ en clang en linux ... no sé cómo publicarlo aquí para que pueda verlo en este enlace compilado clang++ -std=c++17 -O0 -ggdb -fsanitize=thread -stdlib=libc++ -o x x.cpp, no sucede con libstdc ++,
xception

Respuestas:

1

Creo que esto podría ser un error con libc ++ y clang en su implementación débil_ptr / shared_ptr ...

cambiar la cola weak_ptr a una shared_ptr soluciona el problema incluso con versiones antiguas de clang.

los cambios son la línea 25:

  void add(const std::shared_ptr<float> subscriber) {

línea 33:

  std::queue<std::shared_ptr<float>> requests;

y reescribiendo el while en la línea 42 como:

  while (--numIterations) {
    auto subscriber = std::make_shared<float>();

    foo.add(move(subscriber));
  }
xception
fuente
¿has podido reproducirlo? ¿Qué versión de clang?
Marek R
Para mí esto suena lógico, ya que esto evita que los objetos sean destruidos. La condición de carrera parece ocurrir en el destructor de shared_ptr.
JHBonarius
clang ++ -v clang versión 9.0.0 (etiquetas / RELEASE_900 / final) Destino: x86_64-pc-linux-gnu Modelo de subproceso: posixDir instalado: / usr / lib / llvm / 9 / bin Instalación seleccionada de GCC: / usr / lib / gcc /x86_64-pc-linux-gnu/9.2.0 Candidato multilib:.; @ m64 Candidato multilib: 32; @ m32 Seleccionado multilib:.; @ m64 @MarekR
xception
@MarekR Incluso publiqué mi propia traza inversa con un enlace a ella en un comentario sobre la pregunta y construí banderas
xception
1
@JHBonarius si entiendo correctamente cómo las interacciones débiles_ptr / shared_ptr deberían funcionar, usarlas desde diferentes hilos debería ser seguro (no los datos puntiagudos, solo las clases envolventes) al menos en cómo el OP los usa con un shared_ptr y un débil_ptr (siendo diferentes objetos )
xception