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
131 lines (117 loc) · 4.41 KB
/
PoolManager.cs
File metadata and controls
131 lines (117 loc) · 4.41 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
131
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 object();
static volatile (string Key, ConnectorPool Pool)[] _pools = new (string, ConnectorPool)[InitialPoolsSize];
static volatile int _nextSlot;
internal static (string Key, ConnectorPool Pool)[] Pools => _pools;
internal static bool TryGetValue(string key, [NotNullWhen(true)] out ConnectorPool? 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 writte, 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 ConnectorPool GetOrAdd(string key, ConnectorPool 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, ConnectorPool)[_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, ConnectorPool)[InitialPoolsSize];
_nextSlot = 0;
}
}
}
}