| @@ -19,6 +19,9 @@ | |||||
| <Compile Include="..\Discord.Net.WebSocket\Net\DefaultWebSocketClient.cs"> | <Compile Include="..\Discord.Net.WebSocket\Net\DefaultWebSocketClient.cs"> | ||||
| <Link>Net\DefaultWebSocketClient.cs</Link> | <Link>Net\DefaultWebSocketClient.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.WebSocket\ConnectionManager.cs"> | |||||
| <Link>ConnectionManager.cs</Link> | |||||
| </Compile> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | ||||
| @@ -12,12 +12,12 @@ namespace Discord.Rpc | |||||
| remove { _connectedEvent.Remove(value); } | remove { _connectedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>(); | private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>(); | ||||
| public event Func<Exception, bool, Task> Disconnected | |||||
| public event Func<Exception, Task> Disconnected | |||||
| { | { | ||||
| add { _disconnectedEvent.Add(value); } | add { _disconnectedEvent.Add(value); } | ||||
| remove { _disconnectedEvent.Remove(value); } | remove { _disconnectedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<Exception, bool, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, bool, Task>>(); | |||||
| private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>(); | |||||
| public event Func<Task> Ready | public event Func<Task> Ready | ||||
| { | { | ||||
| add { _readyEvent.Add(value); } | add { _readyEvent.Add(value); } | ||||
| @@ -40,6 +40,8 @@ namespace Discord.Rpc | |||||
| _rpcLogger = LogManager.CreateLogger("RPC"); | _rpcLogger = LogManager.CreateLogger("RPC"); | ||||
| _connection = new ConnectionManager(_stateLock, _rpcLogger, config.ConnectionTimeout, | _connection = new ConnectionManager(_stateLock, _rpcLogger, config.ConnectionTimeout, | ||||
| OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | ||||
| _connection.Connected += () => _connectedEvent.InvokeAsync(); | |||||
| _connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex); | |||||
| _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | ||||
| _serializer.Error += (s, e) => | _serializer.Error += (s, e) => | ||||
| @@ -59,28 +59,28 @@ namespace Discord.Audio | |||||
| internal AudioClient(SocketGuild guild, int id) | internal AudioClient(SocketGuild guild, int id) | ||||
| { | { | ||||
| Guild = guild; | Guild = guild; | ||||
| _audioLogger = Discord.LogManager.CreateLogger($"Audio #{id}"); | |||||
| ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider); | |||||
| ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | |||||
| ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").ConfigureAwait(false); | |||||
| //ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false); | |||||
| ApiClient.ReceivedEvent += ProcessMessageAsync; | |||||
| ApiClient.ReceivedPacket += ProcessPacketAsync; | |||||
| _stateLock = new SemaphoreSlim(1, 1); | _stateLock = new SemaphoreSlim(1, 1); | ||||
| _connection = new ConnectionManager(_stateLock, _audioLogger, 30000, | _connection = new ConnectionManager(_stateLock, _audioLogger, 30000, | ||||
| OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | ||||
| _connection.Connected += () => _connectedEvent.InvokeAsync(); | |||||
| _connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex); | |||||
| _heartbeatTimes = new ConcurrentQueue<long>(); | _heartbeatTimes = new ConcurrentQueue<long>(); | ||||
| _audioLogger = Discord.LogManager.CreateLogger($"Audio #{id}"); | |||||
| _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | ||||
| _serializer.Error += (s, e) => | _serializer.Error += (s, e) => | ||||
| { | { | ||||
| _audioLogger.WarningAsync(e.ErrorContext.Error).GetAwaiter().GetResult(); | _audioLogger.WarningAsync(e.ErrorContext.Error).GetAwaiter().GetResult(); | ||||
| e.ErrorContext.Handled = true; | e.ErrorContext.Handled = true; | ||||
| }; | |||||
| ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider); | |||||
| ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | |||||
| ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").ConfigureAwait(false); | |||||
| //ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false); | |||||
| ApiClient.ReceivedEvent += ProcessMessageAsync; | |||||
| ApiClient.ReceivedPacket += ProcessPacketAsync; | |||||
| }; | |||||
| LatencyUpdated += async (old, val) => await _audioLogger.VerboseAsync($"Latency = {val} ms").ConfigureAwait(false); | LatencyUpdated += async (old, val) => await _audioLogger.VerboseAsync($"Latency = {val} ms").ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -98,25 +98,32 @@ namespace Discord.Audio | |||||
| private async Task OnConnectingAsync() | private async Task OnConnectingAsync() | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Connecting ApiClient").ConfigureAwait(false); | |||||
| await ApiClient.ConnectAsync("wss://" + _url).ConfigureAwait(false); | await ApiClient.ConnectAsync("wss://" + _url).ConfigureAwait(false); | ||||
| await _audioLogger.DebugAsync("Sending Identity").ConfigureAwait(false); | |||||
| await ApiClient.SendIdentityAsync(_userId, _sessionId, _token).ConfigureAwait(false); | await ApiClient.SendIdentityAsync(_userId, _sessionId, _token).ConfigureAwait(false); | ||||
| //Wait for READY | |||||
| await _connection.WaitAsync().ConfigureAwait(false); | |||||
| } | } | ||||
| private async Task OnDisconnectingAsync(Exception ex) | private async Task OnDisconnectingAsync(Exception ex) | ||||
| { | { | ||||
| //Disconnect from server | |||||
| await _audioLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false); | |||||
| await ApiClient.DisconnectAsync().ConfigureAwait(false); | await ApiClient.DisconnectAsync().ConfigureAwait(false); | ||||
| //Wait for tasks to complete | //Wait for tasks to complete | ||||
| await _audioLogger.DebugAsync("Waiting for heartbeater").ConfigureAwait(false); | |||||
| var heartbeatTask = _heartbeatTask; | var heartbeatTask = _heartbeatTask; | ||||
| if (heartbeatTask != null) | if (heartbeatTask != null) | ||||
| await heartbeatTask.ConfigureAwait(false); | await heartbeatTask.ConfigureAwait(false); | ||||
| _heartbeatTask = null; | _heartbeatTask = null; | ||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(Guild.Id, null, false, false).ConfigureAwait(false); | |||||
| long time; | long time; | ||||
| while (_heartbeatTimes.TryDequeue(out time)) { } | while (_heartbeatTimes.TryDequeue(out time)) { } | ||||
| _lastMessageTime = 0; | _lastMessageTime = 0; | ||||
| await _audioLogger.DebugAsync("Sending Voice State").ConfigureAwait(false); | |||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(Guild.Id, null, false, false).ConfigureAwait(false); | |||||
| } | } | ||||
| public AudioOutStream CreateOpusStream(int samplesPerFrame, int bufferMillis) | public AudioOutStream CreateOpusStream(int samplesPerFrame, int bufferMillis) | ||||
| @@ -2,6 +2,7 @@ using Discord.Logging; | |||||
| using System; | using System; | ||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Discord.Net; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| @@ -39,7 +40,13 @@ namespace Discord | |||||
| clientDisconnectHandler(ex => | clientDisconnectHandler(ex => | ||||
| { | { | ||||
| if (ex != null) | if (ex != null) | ||||
| Error(new Exception("WebSocket connection was closed", ex)); | |||||
| { | |||||
| var ex2 = ex as WebSocketClosedException; | |||||
| if (ex2?.CloseCode == 4006) | |||||
| CriticalError(new Exception("WebSocket session expired", ex)); | |||||
| else | |||||
| Error(new Exception("WebSocket connection was closed", ex)); | |||||
| } | |||||
| else | else | ||||
| Error(new Exception("WebSocket connection was closed")); | Error(new Exception("WebSocket connection was closed")); | ||||
| return Task.Delay(0); | return Task.Delay(0); | ||||
| @@ -50,7 +57,7 @@ namespace Discord | |||||
| { | { | ||||
| await AcquireConnectionLock().ConfigureAwait(false); | await AcquireConnectionLock().ConfigureAwait(false); | ||||
| var reconnectCancelToken = new CancellationTokenSource(); | var reconnectCancelToken = new CancellationTokenSource(); | ||||
| _reconnectCancelToken = new CancellationTokenSource(); | |||||
| _reconnectCancelToken = reconnectCancelToken; | |||||
| _task = Task.Run(async () => | _task = Task.Run(async () => | ||||
| { | { | ||||
| try | try | ||||
| @@ -95,6 +95,8 @@ namespace Discord.WebSocket | |||||
| _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); | _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); | ||||
| _connection = new ConnectionManager(_stateLock, _gatewayLogger, config.ConnectionTimeout, | _connection = new ConnectionManager(_stateLock, _gatewayLogger, config.ConnectionTimeout, | ||||
| OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | ||||
| _connection.Connected += () => _connectedEvent.InvokeAsync(); | |||||
| _connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex); | |||||
| _nextAudioId = 1; | _nextAudioId = 1; | ||||
| _connectionGroupLock = groupLock; | _connectionGroupLock = groupLock; | ||||
| @@ -173,8 +175,6 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Connecting ApiClient").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Connecting ApiClient").ConfigureAwait(false); | ||||
| await ApiClient.ConnectAsync().ConfigureAwait(false); | await ApiClient.ConnectAsync().ConfigureAwait(false); | ||||
| await _gatewayLogger.DebugAsync("Raising Event").ConfigureAwait(false); | |||||
| await _connectedEvent.InvokeAsync().ConfigureAwait(false); | |||||
| if (_sessionId != null) | if (_sessionId != null) | ||||
| { | { | ||||
| @@ -189,7 +189,7 @@ namespace Discord.WebSocket | |||||
| //Wait for READY | //Wait for READY | ||||
| await _connection.WaitAsync().ConfigureAwait(false); | await _connection.WaitAsync().ConfigureAwait(false); | ||||
| await _gatewayLogger.DebugAsync("Sending Status").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Sending Status").ConfigureAwait(false); | ||||
| await SendStatusAsync().ConfigureAwait(false); | await SendStatusAsync().ConfigureAwait(false); | ||||
| @@ -506,15 +506,16 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| internal async Task FinishConnectAudio(int id, string url, string token) | internal async Task FinishConnectAudio(int id, string url, string token) | ||||
| { | { | ||||
| //TODO: Mem Leak: Disconnected/Connected handlers arent cleaned up | |||||
| var voiceState = GetVoiceState(Discord.CurrentUser.Id).Value; | var voiceState = GetVoiceState(Discord.CurrentUser.Id).Value; | ||||
| await _audioLock.WaitAsync().ConfigureAwait(false); | await _audioLock.WaitAsync().ConfigureAwait(false); | ||||
| try | try | ||||
| { | { | ||||
| var promise = _audioConnectPromise; | |||||
| if (_audioClient == null) | if (_audioClient == null) | ||||
| { | { | ||||
| var audioClient = new AudioClient(this, id); | var audioClient = new AudioClient(this, id); | ||||
| var promise = _audioConnectPromise; | |||||
| audioClient.Disconnected += async ex => | audioClient.Disconnected += async ex => | ||||
| { | { | ||||
| if (!promise.Task.IsCompleted) | if (!promise.Task.IsCompleted) | ||||
| @@ -532,7 +533,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| _audioClient.Connected += () => | _audioClient.Connected += () => | ||||
| { | { | ||||
| var _ = _audioConnectPromise.TrySetResultAsync(_audioClient); | |||||
| var _ = promise.TrySetResultAsync(_audioClient); | |||||
| return Task.Delay(0); | return Task.Delay(0); | ||||
| }; | }; | ||||
| await _audioClient.StartAsync(url, Discord.CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false); | await _audioClient.StartAsync(url, Discord.CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false); | ||||