ServiceHubRemoteHostClient.ConnectionManager.cs 3.8 KB
Newer Older
J
Jonathon Marolf 已提交
1 2 3
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
4

5 6
#nullable enable

7 8 9 10 11 12 13 14
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using Roslyn.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.Remote
{
15
    internal sealed partial class ServiceHubRemoteHostClient
16
    {
17
        private delegate Task<Connection> ConnectionFactory(string serviceName, CancellationToken cancellationToken);
18

19 20 21
        private sealed partial class ConnectionPool : IDisposable
        {
            private readonly ConnectionFactory _connectionFactory;
22 23 24 25
            private readonly ReaderWriterLockSlim _shutdownLock;
            private readonly int _maxPoolConnections;

            // keyed to serviceName. each connection is for specific service such as CodeAnalysisService
26
            private readonly ConcurrentDictionary<string, ConcurrentQueue<Connection>> _pools;
27

28
            private bool _isDisposed;
29

30
            public ConnectionPool(ConnectionFactory connectionFactory, int maxPoolConnection)
31
            {
32
                _connectionFactory = connectionFactory;
33 34 35 36
                _maxPoolConnections = maxPoolConnection;

                // initial value 4 is chosen to stop concurrent dictionary creating too many locks.
                // and big enough for all our services such as codeanalysis, remotehost, snapshot and etc services
37
                _pools = new ConcurrentDictionary<string, ConcurrentQueue<Connection>>(concurrencyLevel: 4, capacity: 4);
38 39 40 41

                _shutdownLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
            }

42
            public async Task<Connection> GetOrCreateConnectionAsync(string serviceName, CancellationToken cancellationToken)
43
            {
44
                var queue = _pools.GetOrAdd(serviceName, _ => new ConcurrentQueue<Connection>());
45 46 47 48 49
                if (queue.TryDequeue(out var connection))
                {
                    return new PooledConnection(this, serviceName, connection);
                }

50 51
                var newConnection = await _connectionFactory(serviceName, cancellationToken).ConfigureAwait(false);
                return new PooledConnection(this, serviceName, newConnection);
52 53
            }

54
            private void Free(string serviceName, Connection connection)
55 56 57
            {
                using (_shutdownLock.DisposableRead())
                {
58
                    if (_isDisposed)
59
                    {
60 61
                        // pool is not being used or 
                        // manager is already shutdown
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
                        connection.Dispose();
                        return;
                    }

                    // queue must exist
                    var queue = _pools[serviceName];
                    if (queue.Count >= _maxPoolConnections)
                    {
                        // let the connection actually go away
                        connection.Dispose();
                        return;
                    }

                    // pool the connection
                    queue.Enqueue(connection);
                }
            }

80
            public void Dispose()
81 82 83
            {
                using (_shutdownLock.DisposableWrite())
                {
84
                    _isDisposed = true;
85

86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
                    // let all connections in the pool to go away
                    foreach (var (_, queue) in _pools)
                    {
                        while (queue.TryDequeue(out var connection))
                        {
                            connection.Dispose();
                        }
                    }

                    _pools.Clear();
                }
            }
        }
    }
}