-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathConnectionPool.cs
More file actions
135 lines (105 loc) · 4.63 KB
/
ConnectionPool.cs
File metadata and controls
135 lines (105 loc) · 4.63 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
using Microsoft.AnalysisServices.AdomdClient;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Xmla.Func.Proxy
{
//From https://github.com/microsoft/azure-analysis-services-http-sample
public class ConnectionPoolEntry
{
public ConnectionPoolEntry(AdomdConnection con, string connectionString)
{
this.Connection = con;
this.ConnectionString = connectionString;
//the combindation of the strong reference to the connection
//and this delegate ties the reachability of the ConnectionPoolEntry and the AdomdConnection together
//so they are guaranteed to become unreachable at the same time
//This would enable the ConnectionPool to keep a WeakReference to the ConnectionPoolEntry without
//keeping the AdomdConnection alive, but also not worry about the ConnectionPoolEntry being GCd
//while the AdomdConnection is still alive.
con.Disposed += (s, a) =>
{
this.IsDisposed = true;
con = null;
};
}
public bool IsDisposed { get; private set; } = false;
[System.Text.Json.Serialization.JsonIgnore]
public string ConnectionString { get; private set; }
[System.Text.Json.Serialization.JsonIgnore]
public AdomdConnection Connection { get; private set; }
public DateTime ValidTo { get; set; }
public void RecordCheckIn()
{
IsCheckedOut = false;
TotalCheckoutTime += DateTime.Now.Subtract(LastCheckedOut);
LastCheckedIn = DateTime.Now;
}
public void RecordCheckOut()
{
IsCheckedOut = true;
LastCheckedOut = DateTime.Now;
TimesCheckedOut += 1;
}
public bool IsCheckedOut { get; private set; }
public int TimesCheckedOut { get; private set; } = 0;
[System.Text.Json.Serialization.JsonIgnore]
public TimeSpan TotalCheckoutTime { get; private set; }
public DateTime LastCheckedOut { get; private set; } = DateTime.MinValue;
public DateTime LastCheckedIn { get; private set; } = DateTime.MinValue;
public DateTime CreatedAt { get; private set; } = DateTime.Now;
public override string ToString()
{
return System.Text.Json.JsonSerializer.Serialize(this);
}
}
//This is a simple Connection Pool, made simple because the client
//is responsible for checking out and back in the ConnectionPoolEntry, not just the AdomdConneciton.
//The ConnectionPoolEntry has a reference to the AdomdConnection and vice versa so they have the same lifetime.
//If a client checks out a ConnectionPoolEntry and doesn't return it, the ConnectionPool retains no reference to it.
public class ConnectionPool
{
ConcurrentDictionary<string, ConcurrentStack<ConnectionPoolEntry>> avalableConnections = new ConcurrentDictionary<string, ConcurrentStack<ConnectionPoolEntry>>();
public void ReturnConnection(ConnectionPoolEntry entry)
{
var key = entry.ConnectionString;
entry.RecordCheckIn();
avalableConnections[key].Push(entry);
}
public ConnectionPoolEntry GetConnection(string connectionString)
{
var key = connectionString;
ConnectionPoolEntry rv = null;
avalableConnections.AddOrUpdate(key, k => new ConcurrentStack<ConnectionPoolEntry>(), (k, c) =>
{
while (c.TryPop( out var entry ))
{
//if we discover that the entry has expired, dispose of it
//this typically happens when the entry uses BEARER auth and its token
//has expired (or is about to).
if ( DateTime.Now > entry.ValidTo.Subtract(TimeSpan.FromMinutes(1)) )
{
entry.Connection.Dispose();
continue;
}
rv = entry;
break;
}
return c;
});
if (rv == null)
{
var con = new AdomdConnection(connectionString);
rv = new ConnectionPoolEntry(con, connectionString);
var validTo = DateTime.Now.AddMinutes(5); //default
rv.ValidTo = validTo;
con.Open();
}
rv.RecordCheckOut();
return rv;
}
}
}