-
-
Notifications
You must be signed in to change notification settings - Fork 90
Expand file tree
/
Copy pathConnectionManager.cs
More file actions
150 lines (122 loc) · 4.84 KB
/
Copy pathConnectionManager.cs
File metadata and controls
150 lines (122 loc) · 4.84 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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
using System.Collections.Concurrent;
using System.Threading;
namespace DuckDB.NET.Data.Connection;
/// <summary>
/// Creates, caches, and disconnects ConnectionReferences.
/// </summary>
internal class ConnectionManager
{
public static readonly ConnectionManager Default = new();
private static readonly ConcurrentDictionary<string, FileReference> ConnectionCache = new(StringComparer.OrdinalIgnoreCase);
internal ConnectionReference GetConnectionReference(DuckDBConnectionString connectionString)
{
var filename = connectionString.DataSource;
var fileRef = connectionString.InMemory && !connectionString.Shared ? new FileReference("") : null;
//need to loop until we have a locked fileRef
//that is also in the cache
while (fileRef == null)
{
fileRef = ConnectionCache.GetOrAdd(filename, fn =>
{
fileRef = new FileReference(filename);
return fileRef;
});
Monitor.Enter(fileRef);
//Need to make sure what we have locked is still in the cache
var existingFileRef = ConnectionCache.GetOrAdd(filename, fileRef);
if (existingFileRef == fileRef)
{
//file in the cache matches what is locked, we are good!
break;
}
//exit lock and try the whole thing again
Monitor.Exit(fileRef);
fileRef = null;
}
//now connect what needs to be connected
try
{
if (fileRef.Database == null)
{
var path = connectionString.InMemory ? null : filename;
NativeMethods.Configuration.DuckDBCreateConfig(out var config);
using (config)
{
foreach (var (option, value) in connectionString.Configuration)
{
var state = NativeMethods.Configuration.DuckDBSetConfig(config, option, value);
if (!state.IsSuccess())
{
throw new DuckDBException($"Error setting '{option}' to '{value}'");
}
}
var resultOpen = NativeMethods.Startup.DuckDBOpen(path, out var db, config, out var error);
if (!resultOpen.IsSuccess())
{
throw new DuckDBException($"DuckDBOpen failed: {error}");
}
fileRef.Database = db;
}
}
var resultConnect = NativeMethods.Startup.DuckDBConnect(fileRef.Database, out var nativeConnection);
if (resultConnect.IsSuccess())
{
fileRef.Increment();
}
else
{
throw new DuckDBException("DuckDBConnect failed");
}
return new ConnectionReference(fileRef, nativeConnection);
}
finally
{
if (Monitor.IsEntered(fileRef))
{
Monitor.Exit(fileRef);
}
}
}
internal void ReturnConnectionReference(ConnectionReference connectionReference)
{
var fileRef = connectionReference.FileReferenceCounter;
lock (fileRef)
{
var nativeConnection = connectionReference.NativeConnection;
nativeConnection.Dispose();
var current = fileRef.Decrement();
if (current < 0)
{
throw new InvalidOperationException($"{fileRef.FileName} has been returned too many times");
}
if (current == 0)
{
fileRef.Database?.Dispose();
fileRef.Database = null;
if (!string.IsNullOrEmpty(fileRef.FileName) && !ConnectionCache.TryRemove(fileRef.FileName, out _))
{
throw new InvalidOperationException($"Internal Error: tried to remove {fileRef.FileName} from cache but it wasn't there!");
}
}
}
}
internal ConnectionReference DuplicateConnectionReference(ConnectionReference connectionReference)
{
var fileRef = connectionReference.FileReferenceCounter;
if (fileRef.Database is null)
throw new InvalidOperationException(); //shouldn't happen if we already have a connection reference
lock (fileRef)
{
var resultConnect = NativeMethods.Startup.DuckDBConnect(fileRef.Database, out var duplicatedNativeConnection);
if (resultConnect.IsSuccess())
{
fileRef.Increment();
}
else
{
throw new DuckDBException("DuckDBConnect failed");
}
return new ConnectionReference(fileRef, duplicatedNativeConnection);
}
}
}