@@ -26,64 +26,78 @@ def setup
2626 # Aliases
2727 @includes_handler = @preprocessinator_includes_handler
2828 @file_handler = @preprocessinator_file_handler
29+
30+ # Thread-safe per-file locking for YAML cache operations
31+ # Key: includes_list_filepath (String), Value: Mutex
32+ @file_locks = { }
33+ @file_locks_mutex = Mutex . new
2934 end
3035
3136
3237 def preprocess_includes ( filepath :, test :, flags :, include_paths :, defines :, deep : false )
3338 includes_list_filepath = @file_path_utils . form_preprocessed_includes_list_filepath ( filepath , test )
3439
35- includes = [ ]
40+ # Get or create a mutex for this specific cache file
41+ file_lock = @file_locks_mutex . synchronize do
42+ @file_locks [ includes_list_filepath ] ||= Mutex . new
43+ end
3644
37- # If existing YAML file of includes is newer than the file we're processing, skip preprocessing
38- if @file_wrapper . newer? ( includes_list_filepath , filepath )
39- msg = @reportinator . generate_module_progress (
40- operation : "Loading #include statement listing file for" ,
41- module_name : test ,
42- filename : File . basename ( filepath )
43- )
44- @loginator . log ( msg , Verbosity ::NORMAL )
45-
46- # Note: It's possible empty YAML content returns nil
47- includes = @yaml_wrapper . load ( includes_list_filepath )
48-
49- msg = "Loaded existing #include list from #{ includes_list_filepath } :"
50-
51- if includes . nil? or includes . empty?
52- # Ensure includes defaults to emtpy array to prevent external iteration problems
53- includes = [ ]
54- msg += ' <empty>'
55- else
56- includes . each { |include | msg += "\n - #{ include } " }
57- end
45+ includes = [ ]
5846
59- @loginator . log ( msg , Verbosity ::DEBUG )
60- @loginator . log ( '' , Verbosity ::DEBUG )
61-
62- # Full preprocessing-based #include extraction with saving to YAML file
63- else
64- includes = @includes_handler . extract_includes (
65- filepath : filepath ,
66- test : test ,
67- flags : flags ,
68- include_paths : include_paths ,
69- defines : defines ,
70- deep : deep
71- )
72-
73- msg = "Extracted #include list from #{ filepath } :"
74-
75- if includes . nil? or includes . empty?
76- # Ensure includes defaults to emtpy array to prevent external iteration problems
77- includes = [ ]
78- msg += ' <empty>'
47+ # Wrap the entire check-read-or-extract-write operation in a mutex
48+ # This prevents race conditions when multiple threads process the same file
49+ file_lock . synchronize do
50+ # If existing YAML file of includes is newer than the file we're processing, skip preprocessing
51+ if @file_wrapper . newer? ( includes_list_filepath , filepath )
52+ msg = @reportinator . generate_module_progress (
53+ operation : "Loading #include statement listing file for" ,
54+ module_name : test ,
55+ filename : File . basename ( filepath )
56+ )
57+ @loginator . log ( msg , Verbosity ::NORMAL )
58+
59+ # Note: It's possible empty YAML content returns nil
60+ includes = @yaml_wrapper . load ( includes_list_filepath )
61+
62+ msg = "Loaded existing #include list from #{ includes_list_filepath } :"
63+
64+ if includes . nil? or includes . empty?
65+ # Ensure includes defaults to emtpy array to prevent external iteration problems
66+ includes = [ ]
67+ msg += ' <empty>'
68+ else
69+ includes . each { |include | msg += "\n - #{ include } " }
70+ end
71+
72+ @loginator . log ( msg , Verbosity ::DEBUG )
73+ @loginator . log ( '' , Verbosity ::DEBUG )
74+
75+ # Full preprocessing-based #include extraction with saving to YAML file
7976 else
80- includes . each { |include | msg += "\n - #{ include } " }
77+ includes = @includes_handler . extract_includes (
78+ filepath : filepath ,
79+ test : test ,
80+ flags : flags ,
81+ include_paths : include_paths ,
82+ defines : defines ,
83+ deep : deep
84+ )
85+
86+ msg = "Extracted #include list from #{ filepath } :"
87+
88+ if includes . nil? or includes . empty?
89+ # Ensure includes defaults to emtpy array to prevent external iteration problems
90+ includes = [ ]
91+ msg += ' <empty>'
92+ else
93+ includes . each { |include | msg += "\n - #{ include } " }
94+ end
95+
96+ @loginator . log ( msg , Verbosity ::DEBUG )
97+ @loginator . log ( '' , Verbosity ::DEBUG )
98+
99+ @includes_handler . write_includes_list ( includes_list_filepath , includes )
81100 end
82-
83- @loginator . log ( msg , Verbosity ::DEBUG )
84- @loginator . log ( '' , Verbosity ::DEBUG )
85-
86- @includes_handler . write_includes_list ( includes_list_filepath , includes )
87101 end
88102
89103 return includes
@@ -102,7 +116,7 @@ def preprocess_mockable_header_file(filepath:, test:, flags:, include_paths:, de
102116 test : test ,
103117 flags : flags ,
104118 include_paths : include_paths ,
105- defines : defines
119+ defines : defines
106120 }
107121
108122 # Trigger pre_mock_preprocessing plugin hook
@@ -114,10 +128,10 @@ def preprocess_mockable_header_file(filepath:, test:, flags:, include_paths:, de
114128 flags : flags ,
115129 include_paths : include_paths ,
116130 defines : defines ,
117- deep : preprocess_deep
131+ deep : preprocess_deep
118132 }
119133
120- # Extract includes & log progress and details
134+ # Extract includes & log progress and details
121135 includes = preprocess_file_common ( **arg_hash )
122136
123137 arg_hash = {
@@ -140,7 +154,7 @@ def preprocess_mockable_header_file(filepath:, test:, flags:, include_paths:, de
140154 preprocessed_filepath : preprocessed_filepath ,
141155 contents : contents ,
142156 extras : extras ,
143- includes : includes
157+ includes : includes
144158 }
145159
146160 # Create a reconstituted header file from preprocessing expansion and preserving any extras
@@ -165,7 +179,7 @@ def preprocess_test_file(filepath:, test:, flags:, include_paths:, defines:)
165179 test : test ,
166180 flags : flags ,
167181 include_paths : include_paths ,
168- defines : defines
182+ defines : defines
169183 }
170184
171185 # Trigger pre_test_preprocess plugin hook
@@ -177,7 +191,7 @@ def preprocess_test_file(filepath:, test:, flags:, include_paths:, defines:)
177191 flags : flags ,
178192 include_paths : include_paths ,
179193 defines : defines ,
180- deep : preprocess_deep
194+ deep : preprocess_deep
181195 }
182196
183197 # Extract includes & log progress and info
@@ -188,7 +202,7 @@ def preprocess_test_file(filepath:, test:, flags:, include_paths:, defines:)
188202 test : test ,
189203 flags : flags ,
190204 include_paths : include_paths ,
191- defines : defines
205+ defines : defines
192206 }
193207
194208 # `contents` & `extras` are arrays of text strings to be assembled in generating a new test file.
@@ -200,7 +214,7 @@ def preprocess_test_file(filepath:, test:, flags:, include_paths:, defines:)
200214 preprocessed_filepath : preprocessed_filepath ,
201215 contents : contents ,
202216 extras : extras ,
203- includes : includes
217+ includes : includes
204218 }
205219
206220 # Create a reconstituted test file from preprocessing expansion and preserving any extras
@@ -231,7 +245,7 @@ def preprocess_file_common(filepath:, test:, flags:, include_paths:, defines:, d
231245 flags : flags ,
232246 include_paths : include_paths ,
233247 defines : defines ,
234- deep : deep )
248+ deep : deep )
235249
236250 return includes
237251 end
0 commit comments