@@ -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); | ||||