Chromium Chronicle n.o 25: Anotaciones de seguridad de los subprocesos

Episodio 25: de Victor Costan en SFO (octubre de 2021)
Episodios anteriores

En C++, descartar la posibilidad de carreras de datos se reduce a una pequeña prueba de corrección de la seguridad de los subprocesos para el acceso de cada miembro de datos. Estas pruebas suman mucho trabajo mental, especialmente cuando se revisa o refactoriza el código. El framework de análisis estático de Clang se encarga del trabajo repetitivo de las pruebas de seguridad de subprocesos.

Agrega GUARDED_BY_CONTEXT() a los miembros de datos en clases no seguras para subprocesos.

La mayoría de las clases de Chrome no son seguras para los subprocesos y deben usarse en una sola secuencia. Agrega anotaciones a todos los miembros de datos que no sean seguros para los subprocesos. Las anotaciones innecesarias son seguras, pero si faltan anotaciones, existe el riesgo de que se produzcan carreras de datos.

#include "base/sequence_checker.h"  // for SEQUENCE_CHECKER()
#include "base/thread_annotations.h"  // for GUARDED_BY_CONTEXT()

class Cache {
  // Methods here.
 private:
  SEQUENCE_CHECKER(sequence_checker_);
  base::flat_map<std::string, std::string> data_ GUARDED_BY_CONTEXT(sequence_checker_);
};

Clang aplica verificaciones de secuencias

A cambio de anotar los miembros de los datos, Clang garantiza que cualquier método que acceda a los datos realice una verificación de seguridad de la secuencia antes de hacerlo. A medida que se mueve el código en las refactorizaciones, Clang continúa aplicando la anotación GUARDED_BY_CONTEXT().

void Cache::Set(base::StringPiece key, base::StringPiece value) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);  // Clang warns without this.
  data_.emplace(key, value);
}

Agrega GUARDED_BY() a los miembros de datos en clases seguras para subprocesos que usan exclusiones mutuas.

Algunas clases en Chrome deben usar bloqueos para proteger los subprocesos. En estos casos, anota todos los miembros de datos que no sean seguros para los subprocesos. Cada anotación apunta a una exclusión mutua que debe conservarse mientras se accede al miembro de datos.

#include "base/thread_annotations.h"  // for GUARDED_BY()

class ThreadSafeCache {
  // Methods here.
  private:
    base::Lock lock_;
    base::flat_map<std::string, std::string> data_ GUARDED_BY(lock_);
};

Clang aplica las adquisiciones de bloqueo

Espera y deja que el compilador se asegure de que cada base::AutoLock tenga el alcance correcto. y que las llamadas de bloqueo Acquire() y Release() se sincronicen correctamente.

void ThreadSafeCache::Set(base::StringPiece key, base::StringPiece value) {
  base::AutoLock auto_lock(lock_);  // Clang warns without this.
  data_.emplace(key, value);
}