Skip to content

Commit 91420c4

Browse files
Slutaustinv900
authored andcommitted
Fix hook subscription modification during hook call
1 parent 15e0164 commit 91420c4

File tree

1 file changed

+131
-38
lines changed

1 file changed

+131
-38
lines changed

src/Plugins/PluginManager.cs

Lines changed: 131 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,18 @@ public sealed class PluginManager
3737
// All hook subscriptions
3838
private readonly IDictionary<string, IList<Plugin>> hookSubscriptions;
3939

40+
// The current depth of hook calls
41+
private int hookCallDepth;
42+
4043
// Stores the last time a deprecation warning was printed for a specific hook
4144
private readonly Dictionary<string, float> lastDeprecatedWarningAt = new Dictionary<string, float>();
4245

46+
// Pending hook subscriptions
47+
private readonly Dictionary<string, IList<Plugin>> pendingHookSubscriptions = new Dictionary<string, IList<Plugin>>();
48+
49+
// Pending hook unsubscriptions
50+
private readonly Dictionary<string, IList<Plugin>> pendingHookUnsubscriptions = new Dictionary<string, IList<Plugin>>();
51+
4352
// Re-usable conflict list used for hook calls
4453
private readonly List<string> hookConflicts = new List<string>();
4554

@@ -125,7 +134,25 @@ internal void SubscribeToHook(string hook, Plugin plugin)
125134
return;
126135
}
127136

128-
if (!hookSubscriptions.TryGetValue(hook, out IList<Plugin> sublist))
137+
IList<Plugin> sublist;
138+
// Avoids modifying the plugin list while iterating over it during a hook call
139+
if (hookCallDepth > 0)
140+
{
141+
if (!pendingHookSubscriptions.TryGetValue(hook, out sublist))
142+
{
143+
sublist = new List<Plugin>();
144+
pendingHookSubscriptions.Add(hook, sublist);
145+
}
146+
147+
if (!sublist.Contains(plugin))
148+
{
149+
sublist.Add(plugin);
150+
}
151+
152+
return;
153+
}
154+
155+
if (!hookSubscriptions.TryGetValue(hook, out sublist))
129156
{
130157
sublist = new List<Plugin>();
131158
hookSubscriptions.Add(hook, sublist);
@@ -149,7 +176,24 @@ internal void UnsubscribeToHook(string hook, Plugin plugin)
149176
return;
150177
}
151178

152-
if (hookSubscriptions.TryGetValue(hook, out IList<Plugin> sublist) && sublist.Contains(plugin))
179+
IList<Plugin> sublist;
180+
// Avoids modifying the plugin list while iterating over it during a hook call
181+
if (hookCallDepth > 0)
182+
{
183+
if (!pendingHookUnsubscriptions.TryGetValue(hook, out sublist))
184+
{
185+
sublist = new List<Plugin>();
186+
pendingHookUnsubscriptions.Add(hook, sublist);
187+
}
188+
189+
if (!sublist.Contains(plugin))
190+
{
191+
sublist.Add(plugin);
192+
}
193+
194+
return;
195+
}
196+
if (hookSubscriptions.TryGetValue(hook, out sublist) && sublist.Contains(plugin))
153197
{
154198
sublist.Remove(plugin);
155199
}
@@ -180,64 +224,113 @@ public object CallHook(string hook, params object[] args)
180224
int returnCount = 0;
181225
object finalValue = null;
182226
Plugin finalPlugin = null;
183-
for (int i = 0; i < plugins.Count; i++)
184-
{
185-
// Call the hook
186-
object value = plugins[i].CallHook(hook, args);
187-
if (value != null)
188-
{
189-
values[i] = value;
190-
finalValue = value;
191-
finalPlugin = plugins[i];
192-
returnCount++;
193-
}
194-
}
195227

196-
// Is there a return value?
197-
if (returnCount == 0)
198-
{
199-
ArrayPool.Free(values);
200-
return null;
201-
}
228+
hookCallDepth++;
202229

203-
if (returnCount > 1 && finalValue != null)
230+
try
204231
{
205-
// Notify log of hook conflict
206-
hookConflicts.Clear();
207232
for (int i = 0; i < plugins.Count; i++)
208233
{
209-
object value = values[i];
210-
if (value == null)
234+
// Call the hook
235+
object value = plugins[i].CallHook(hook, args);
236+
if (value != null)
211237
{
212-
continue;
238+
values[i] = value;
239+
finalValue = value;
240+
finalPlugin = plugins[i];
241+
returnCount++;
213242
}
243+
}
244+
245+
// Is there a return value?
246+
if (returnCount == 0)
247+
{
248+
ArrayPool.Free(values);
249+
return null;
250+
}
214251

215-
if (value.GetType().IsValueType)
252+
if (returnCount > 1 && finalValue != null)
253+
{
254+
// Notify log of hook conflict
255+
hookConflicts.Clear();
256+
for (int i = 0; i < plugins.Count; i++)
216257
{
217-
if (!values[i].Equals(finalValue))
258+
object value = values[i];
259+
if (value == null)
218260
{
219-
hookConflicts.Add($"{plugins[i].Name} - {value} ({value.GetType().Name})");
261+
continue;
220262
}
221-
}
222-
else
223-
{
224-
if (values[i] != finalValue)
263+
264+
if (value.GetType().IsValueType)
225265
{
226-
hookConflicts.Add($"{plugins[i].Name} - {value} ({value.GetType().Name})");
266+
if (!values[i].Equals(finalValue))
267+
{
268+
hookConflicts.Add($"{plugins[i].Name} - {value} ({value.GetType().Name})");
269+
}
227270
}
271+
else
272+
{
273+
if (values[i] != finalValue)
274+
{
275+
hookConflicts.Add($"{plugins[i].Name} - {value} ({value.GetType().Name})");
276+
}
277+
}
278+
}
279+
if (hookConflicts.Count > 0)
280+
{
281+
hookConflicts.Add($"{finalPlugin.Name} ({finalValue} ({finalValue.GetType().Name}))");
282+
Logger.Write(LogType.Warning, "Calling hook {0} resulted in a conflict between the following plugins: {1}", hook, string.Join(", ", hookConflicts.ToArray()));
228283
}
229284
}
230-
if (hookConflicts.Count > 0)
285+
ArrayPool.Free(values);
286+
}
287+
finally
288+
{
289+
hookCallDepth--;
290+
if (hookCallDepth == 0)
231291
{
232-
hookConflicts.Add($"{finalPlugin.Name} ({finalValue} ({finalValue.GetType().Name}))");
233-
Logger.Write(LogType.Warning, "Calling hook {0} resulted in a conflict between the following plugins: {1}", hook, string.Join(", ", hookConflicts.ToArray()));
292+
ProcessHookChanges();
234293
}
235294
}
236-
ArrayPool.Free(values);
237295

238296
return finalValue;
239297
}
240298

299+
private void ProcessHookChanges()
300+
{
301+
foreach (var pair in pendingHookSubscriptions)
302+
{
303+
if (!hookSubscriptions.TryGetValue(pair.Key, out IList<Plugin> list))
304+
{
305+
list = new List<Plugin>();
306+
hookSubscriptions.Add(pair.Key, list);
307+
}
308+
309+
foreach (var plugin in pair.Value)
310+
{
311+
if (!list.Contains(plugin))
312+
{
313+
list.Add(plugin);
314+
}
315+
}
316+
}
317+
318+
pendingHookSubscriptions.Clear();
319+
320+
foreach (var pair in pendingHookUnsubscriptions)
321+
{
322+
if (hookSubscriptions.TryGetValue(pair.Key, out IList<Plugin> list))
323+
{
324+
foreach (var plugin in pair.Value)
325+
{
326+
list.Remove(plugin);
327+
}
328+
}
329+
}
330+
331+
pendingHookUnsubscriptions.Clear();
332+
}
333+
241334
/// <summary>
242335
/// Calls a hook on all plugins of this manager and prints a deprecation warning
243336
/// </summary>

0 commit comments

Comments
 (0)