forked from npgsql/npgsql
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPoolManager.cs
More file actions
130 lines (115 loc) · 3.99 KB
/
PoolManager.cs
File metadata and controls
130 lines (115 loc) · 3.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
namespace Npgsql;
/// <summary>
/// Provides lookup for a pool based on a connection string.
/// </summary>
/// <remarks>
/// <see cref="TryGetValue"/> is lock-free, to avoid contention, but the same isn't
/// true of <see cref="GetOrAdd"/>, which acquires a lock. The calling code always tries
/// <see cref="TryGetValue"/> before trying to <see cref="GetOrAdd"/>.
/// </remarks>
static class PoolManager
{
internal const int InitialPoolsSize = 10;
static readonly object Lock = new();
static volatile (string Key, ConnectorSource Pool)[] _pools = new (string, ConnectorSource)[InitialPoolsSize];
static volatile int _nextSlot;
internal static (string Key, ConnectorSource Pool)[] Pools => _pools;
internal static bool TryGetValue(string key, [NotNullWhen(true)] out ConnectorSource? pool)
{
// Note that pools never get removed. _pools is strictly append-only.
var nextSlot = _nextSlot;
var pools = _pools;
var sw = new SpinWait();
// First scan the pools and do reference equality on the connection strings
for (var i = 0; i < nextSlot; i++)
{
var cp = pools[i];
if (ReferenceEquals(cp.Key, key))
{
// It's possible that this pool entry is currently being written: the connection string
// component has already been written, but the pool component is just about to be. So we
// loop on the pool until it's non-null
while (Volatile.Read(ref cp.Pool) == null)
sw.SpinOnce();
pool = cp.Pool;
return true;
}
}
// Next try value comparison on the strings
for (var i = 0; i < nextSlot; i++)
{
var cp = pools[i];
if (cp.Key == key)
{
// See comment above
while (Volatile.Read(ref cp.Pool) == null)
sw.SpinOnce();
pool = cp.Pool;
return true;
}
}
pool = null;
return false;
}
internal static ConnectorSource GetOrAdd(string key, ConnectorSource pool)
{
lock (Lock)
{
if (TryGetValue(key, out var result))
return result;
// May need to grow the array.
if (_nextSlot == _pools.Length)
{
var newPools = new (string, ConnectorSource)[_pools.Length * 2];
Array.Copy(_pools, newPools, _pools.Length);
_pools = newPools;
}
_pools[_nextSlot].Key = key;
_pools[_nextSlot].Pool = pool;
Interlocked.Increment(ref _nextSlot);
return pool;
}
}
internal static void Clear(string connString)
{
if (TryGetValue(connString, out var pool))
pool.Clear();
}
internal static void ClearAll()
{
lock (Lock)
{
var pools = _pools;
for (var i = 0; i < _nextSlot; i++)
{
var cp = pools[i];
if (cp.Key == null)
return;
cp.Pool?.Clear();
}
}
}
static PoolManager()
{
// When the appdomain gets unloaded (e.g. web app redeployment) attempt to nicely
// close idle connectors to prevent errors in PostgreSQL logs (#491).
AppDomain.CurrentDomain.DomainUnload += (sender, args) => ClearAll();
AppDomain.CurrentDomain.ProcessExit += (sender, args) => ClearAll();
}
/// <summary>
/// Resets the pool manager to its initial state, for test purposes only.
/// Assumes that no other threads are accessing the pool.
/// </summary>
internal static void Reset()
{
lock (Lock)
{
ClearAll();
_pools = new (string, ConnectorSource)[InitialPoolsSize];
_nextSlot = 0;
}
}
}