diff --git a/src/ifcparse/schema.cpp b/src/ifcparse/schema.cpp index 9b56e4e7d2..5a4741c203 100644 --- a/src/ifcparse/schema.cpp +++ b/src/ifcparse/schema.cpp @@ -164,6 +164,15 @@ ifcopenshell::schema_definition::schema_definition(const std::string& name, cons entities_.push_back((**it).as_entity()); } } + + // Force each entity's lazy all_attributes_ cache now, while construction + // is still single-threaded. The schema is a process-wide singleton shared + // read-only across concurrent parsing threads; letting all_attributes() + // populate the cache lazily on first parse would be a data race. + for (const entity* ent : entities_) { + ent->all_attributes(); + } + register_schema(this); } @@ -216,6 +225,7 @@ void ifcopenshell::load_schema_plugins(schema_registry& registry) { } void ifcopenshell::schema_registry::bind(const std::string& schema_name, get_schema_fn get, clear_schema_fn clear, const plugin::module& module) { + std::lock_guard lock(mutex_); auto& entry = entries_[schema_key(schema_name)]; entry.get_ = get; entry.clear_ = clear; @@ -223,11 +233,13 @@ void ifcopenshell::schema_registry::bind(const std::string& schema_name, get_sch } void ifcopenshell::schema_registry::bind(schema_definition* schema) { + std::lock_guard lock(mutex_); auto& entry = entries_[schema_key(schema->name())]; entry.schema_ = schema; } const ifcopenshell::schema_definition* ifcopenshell::schema_registry::get(const std::string& schema_name) { + std::lock_guard lock(mutex_); const auto key = schema_key(schema_name); auto iter = entries_.find(key); if (iter == entries_.end()) { @@ -247,6 +259,7 @@ const ifcopenshell::schema_definition* ifcopenshell::schema_registry::get(const } std::vector ifcopenshell::schema_registry::names() { + std::lock_guard lock(mutex_); std::set seen; for (const auto& pair : entries_) { seen.insert(pair.first); @@ -266,6 +279,7 @@ std::vector ifcopenshell::schema_registry::names() { } void ifcopenshell::schema_registry::clear() { + std::lock_guard lock(mutex_); for (auto& pair : entries_) { if (pair.second.clear_) { pair.second.clear_(); diff --git a/src/ifcparse/schema.h b/src/ifcparse/schema.h index bd7e231f77..ff9abedf58 100644 --- a/src/ifcparse/schema.h +++ b/src/ifcparse/schema.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -521,6 +522,14 @@ class IFC_PARSE_API schema_registry { }; std::map entries_; + + // The registry is a process-wide singleton (schema_registry_instance()) + // reached concurrently — e.g. several IFC files parsed on background + // threads at once. get() lazily loads schema plugins and mutates + // entries_, and re-enters bind() through load_schema_plugin(), so the + // mutex is recursive. Held only briefly; returned schema pointers are + // stable for the process lifetime. + std::recursive_mutex mutex_; }; IFC_PARSE_API schema_registry& schema_registry_instance();