Skip to content

Commit f969fbd

Browse files
authored
Fix NetworkManager._attemptedAuthentications concurrency issues (#1686)
* Fix NetworkManager._attemptedAuthentications concurrency issues * Add ConcurrentSet * Make ConcurrentSet internal * Use Roslyn.Utilities.ConcurrentSet * Satisfy linter
1 parent 61aaf57 commit f969fbd

File tree

2 files changed

+191
-1
lines changed

2 files changed

+191
-1
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
// https://github.com/dotnet/roslyn/blob/6da1274c9d24c2f90a48290394a951b23617f2a3/src/Compilers/Core/Portable/InternalUtilities/ConcurrentSet.cs#L16
6+
7+
using System.Collections;
8+
using System.Collections.Concurrent;
9+
using System.Collections.Generic;
10+
using System.Diagnostics;
11+
12+
// namespace Roslyn.Utilities
13+
namespace PuppeteerSharp.Helpers
14+
{
15+
/// <summary>
16+
/// A concurrent, simplified HashSet.
17+
/// </summary>
18+
/// <typeparam name="T">The type</typeparam>
19+
[DebuggerDisplay("Count = {Count}")]
20+
internal sealed class ConcurrentSet<T> : ICollection<T>
21+
{
22+
/// <summary>
23+
/// The default concurrency level is 2. That means the collection can cope with up to two
24+
/// threads making simultaneous modifications without blocking.
25+
/// Note ConcurrentDictionary's default concurrency level is dynamic, scaling according to
26+
/// the number of processors.
27+
/// </summary>
28+
private const int DefaultConcurrencyLevel = 2;
29+
30+
/// <summary>
31+
/// Taken from ConcurrentDictionary.DEFAULT_CAPACITY
32+
/// </summary>
33+
private const int DefaultCapacity = 31;
34+
35+
/// <summary>
36+
/// The backing dictionary. The values are never used; just the keys.
37+
/// </summary>
38+
private readonly ConcurrentDictionary<T, byte> _dictionary;
39+
40+
/// <summary>
41+
/// Construct a concurrent set with the default concurrency level.
42+
/// </summary>
43+
public ConcurrentSet()
44+
{
45+
_dictionary = new ConcurrentDictionary<T, byte>(DefaultConcurrencyLevel, DefaultCapacity);
46+
}
47+
48+
/// <summary>
49+
/// Construct a concurrent set using the specified equality comparer.
50+
/// </summary>
51+
/// <param name="equalityComparer">The equality comparer for values in the set.</param>
52+
public ConcurrentSet(IEqualityComparer<T> equalityComparer)
53+
{
54+
_dictionary = new ConcurrentDictionary<T, byte>(DefaultConcurrencyLevel, DefaultCapacity, equalityComparer);
55+
}
56+
57+
/// <summary>
58+
/// Obtain the number of elements in the set.
59+
/// </summary>
60+
/// <returns>The number of elements in the set.</returns>
61+
public int Count => _dictionary.Count;
62+
63+
/// <summary>
64+
/// Determine whether the set is empty.</summary>
65+
/// <returns>true if the set is empty; otherwise, false.</returns>
66+
public bool IsEmpty => _dictionary.IsEmpty;
67+
68+
public bool IsReadOnly => false;
69+
70+
/// <summary>
71+
/// Determine whether the given value is in the set.
72+
/// </summary>
73+
/// <param name="value">The value to test.</param>
74+
/// <returns>true if the set contains the specified value; otherwise, false.</returns>
75+
public bool Contains(T value)
76+
{
77+
return _dictionary.ContainsKey(value);
78+
}
79+
80+
/// <summary>
81+
/// Attempts to add a value to the set.
82+
/// </summary>
83+
/// <param name="value">The value to add.</param>
84+
/// <returns>true if the value was added to the set. If the value already exists, this method returns false.</returns>
85+
public bool Add(T value)
86+
{
87+
return _dictionary.TryAdd(value, 0);
88+
}
89+
90+
public void AddRange(IEnumerable<T> values)
91+
{
92+
if (values != null)
93+
{
94+
foreach (var v in values)
95+
{
96+
Add(v);
97+
}
98+
}
99+
}
100+
101+
/// <summary>
102+
/// Attempts to remove a value from the set.
103+
/// </summary>
104+
/// <param name="value">The value to remove.</param>
105+
/// <returns>true if the value was removed successfully; otherwise false.</returns>
106+
public bool Remove(T value)
107+
{
108+
return _dictionary.TryRemove(value, out _);
109+
}
110+
111+
/// <summary>
112+
/// Clear the set
113+
/// </summary>
114+
public void Clear()
115+
{
116+
_dictionary.Clear();
117+
}
118+
119+
public struct KeyEnumerator
120+
{
121+
private readonly IEnumerator<KeyValuePair<T, byte>> _kvpEnumerator;
122+
123+
internal KeyEnumerator(IEnumerable<KeyValuePair<T, byte>> data)
124+
{
125+
_kvpEnumerator = data.GetEnumerator();
126+
}
127+
128+
public T Current => _kvpEnumerator.Current.Key;
129+
130+
public bool MoveNext()
131+
{
132+
return _kvpEnumerator.MoveNext();
133+
}
134+
135+
public void Reset()
136+
{
137+
_kvpEnumerator.Reset();
138+
}
139+
}
140+
141+
/// <summary>
142+
/// Obtain an enumerator that iterates through the elements in the set.
143+
/// </summary>
144+
/// <returns>An enumerator for the set.</returns>
145+
public KeyEnumerator GetEnumerator()
146+
{
147+
// PERF: Do not use dictionary.Keys here because that creates a snapshot
148+
// of the collection resulting in a List<T> allocation. Instead, use the
149+
// KeyValuePair enumerator and pick off the Key part.
150+
return new KeyEnumerator(_dictionary);
151+
}
152+
153+
private IEnumerator<T> GetEnumeratorImpl()
154+
{
155+
// PERF: Do not use dictionary.Keys here because that creates a snapshot
156+
// of the collection resulting in a List<T> allocation. Instead, use the
157+
// KeyValuePair enumerator and pick off the Key part.
158+
foreach (var kvp in _dictionary)
159+
{
160+
yield return kvp.Key;
161+
}
162+
}
163+
164+
IEnumerator<T> IEnumerable<T>.GetEnumerator()
165+
{
166+
return GetEnumeratorImpl();
167+
}
168+
169+
IEnumerator IEnumerable.GetEnumerator()
170+
{
171+
return GetEnumeratorImpl();
172+
}
173+
174+
void ICollection<T>.Add(T item)
175+
{
176+
Add(item);
177+
}
178+
179+
public void CopyTo(T[] array, int arrayIndex)
180+
{
181+
// PERF: Do not use dictionary.Keys here because that creates a snapshot
182+
// of the collection resulting in a List<T> allocation.
183+
// Instead, enumerate the set and copy over the elements.
184+
foreach (var element in this)
185+
{
186+
array[arrayIndex++] = element;
187+
}
188+
}
189+
}
190+
}

lib/PuppeteerSharp/NetworkManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ internal class NetworkManager
2323
private readonly ILogger _logger;
2424
private Dictionary<string, string> _extraHTTPHeaders;
2525
private Credentials _credentials;
26-
private readonly List<string> _attemptedAuthentications = new List<string>();
26+
private readonly ConcurrentSet<string> _attemptedAuthentications = new ConcurrentSet<string>();
2727
private bool _userRequestInterceptionEnabled;
2828
private bool _protocolRequestInterceptionEnabled;
2929
private readonly bool _ignoreHTTPSErrors;

0 commit comments

Comments
 (0)