private async Task InitializeMonitoring(CancellationToken cancellationToken) { await this.monitorCreationTask.Task; this.state = GatewayState.Initializing; try { ServiceEventSource.Current.ServiceMessage(this.Context, "Initializing monitoring {0}", this.myInfo.Id.InstanceId); await gatewayMonitor_.InitializeAsync( this.myInfo, this.heartbeatInterval, cancellationToken); ServiceEventSource.Current.ServiceMessage(this.Context, "Monitoring initialized"); this.state = GatewayState.Monitored; } catch (Exception e) { ServiceEventSource.Current.ServiceMessage(this.Context, "Failed to initialize monitoring - {0}", e.ToString()); this.state = GatewayState.Failed; this.OnMonitoringFailed(); throw e; } }
/// <summary> /// Warning: Do not call from the context of the connect loop! A deadlock will occur! /// </summary> async Task FullDisconnect() { log.LogVerbose("Disconnecting..."); state = GatewayState.Disconnected; handshakeCompleteCancellationSource.Cancel(); if (connectTask != null) { // Cancel any automatic reconnection connectTaskCancellationSource?.Cancel(); // Wait for the automatic reconnection to end try { await connectTask.ConfigureAwait(false); } catch (OperationCanceledException) { /* Expected to happen. */ } catch (Exception ex) { // Should never happen, but there isn't anything we can do here. log.LogError($"[DisconnectAsync] Uncaught exception found in connect task: {ex}"); } } // Disconnect the socket if needed if (socket.CanBeDisconnected) { await socket.DisconnectAsync(WebSocketCloseStatus.NormalClosure, "Disconnecting...", CancellationToken.None) .ConfigureAwait(false); } log.LogVerbose("Disconnected."); }
private void SetGatewayState(GatewayState newState) { switch (newState) { case GatewayState.Disconnected: LogInfo("Disconnected."); break; case GatewayState.ConnectingToPort: LogInfo("Trying to connect..."); break; case GatewayState.ConnectingToGateway: LogInfo("Trying to communicate..."); break; case GatewayState.Connected: LogInfo("Gateway connected."); break; } if (gatewayState == GatewayState.Connected && newState != GatewayState.Connected) { OnDisconnected?.Invoke(); } else if (gatewayState != GatewayState.Connected && newState == GatewayState.Connected) { OnConnected?.Invoke(); } gatewayState = newState; OnGatewayStateChanged?.Invoke(gatewayState); }
private async Task SendHeartbeatAsync(CancellationToken cancellationToken) { ServiceEventSource.Current.ServiceMessage(this.Context, "Sending heartbeat"); try { var cts = CancellationTokenSource.CreateLinkedTokenSource( cancellationToken, new CancellationTokenSource(heartbeatInterval).Token); var gatewayMonitor = ServiceProxy.Create <IGatewayMonitor>( this.monitorServiceAddress, this.monitorServicePartitionKey); await gatewayMonitor.HeartbeatAsync(this.myInfo.Id, cts.Token); ServiceEventSource.Current.ServiceMessage(this.Context, "Heartbeat sent"); } catch (Exception e) { ServiceEventSource.Current.ServiceMessage(this.Context, "Failed to send heartbeat - {0}", e.ToString()); this.state = GatewayState.Failed; this.OnMonitoringFailed(); throw e; } }
private async Task InitializeMonitoring(CancellationToken cancellationToken) { this.state = GatewayState.Initializing; try { ServiceEventSource.Current.ServiceMessage(this.Context, "Initializing monitoring"); var gatewayMonitor = ServiceProxy.Create <IGatewayMonitor>( this.monitorServiceAddress, this.monitorServicePartitionKey); await gatewayMonitor.InitializeAsync( this.myInfo, this.heartbeatInterval, cancellationToken); ServiceEventSource.Current.ServiceMessage(this.Context, "Monitoring initialized"); this.state = GatewayState.Monitored; } catch (Exception e) { ServiceEventSource.Current.ServiceMessage(this.Context, "Failed to initialize monitoring - {0}", e.ToString()); this.state = GatewayState.Failed; this.OnMonitoringFailed(); throw e; } }
void _OnClose(int code = 0) { this._CancelHeartBeat(); if (this.m_CloseRequired) { this.m_ReadyState = GatewayState.Init; return; } if (this.m_Socket != null) { //https://github.com/sta/websocket-sharp/issues/219 if (code == 1015 && this.m_Socket.checkSslProtocolHackFlag()) { this.m_SslHandShakeError = true; } this.m_Socket.Close(); this.m_Socket = null; } this._Reset(isInit: false); bool reset = !this.resumable || this.m_BackOff.fail > 0; if (code != 0) { this.m_CandidateIndex++; } var delay = this._OnFail(reset); NetworkStatusManager.isConnected = false; DebugerUtils.DebugAssert(false, $"connection failed, retry in {delay / 1000f} seconds"); }
public GatewayService(StatelessServiceContext context) : base(context) { this.state = GatewayState.Created; this.myInfo = CreateMyInfo(context); this.heartbeatInterval = GetHeartbeatDuration(context.CodePackageActivationContext); this.monitorServiceAddress = GetMonitorServiceAddress(context.CodePackageActivationContext); this.monitorServicePartitionKey = new ServicePartitionKey(this.myInfo.Id.Name); }
public GatewayStateFormalizer(GatewayState gatewayState) { this.id = gatewayState.Id; this.stateName = gatewayState.StateName; this.remark = gatewayState.Remark; this.createTime = gatewayState.CreateTime .ToString(ConstantService.DateTimeFormat); this.updateTime = gatewayState.UpdateTime .ToString(ConstantService.DateTimeFormat); }
void _Reset(bool isInit, bool reset = true) { this.m_ReadyState = isInit ? GatewayState.Init : GatewayState.CLOSED; this.m_PayloadQueue.Clear(); if (reset) { this.m_SessionId = null; this.m_Sequence = 0; } }
public GatewayService(StatelessServiceContext context) : base(context) { this.state = GatewayState.Created; this.myInfo = CreateMyInfo(context); serviceDownToken = new CancellationTokenSource(); this.heartbeatInterval = GetHeartbeatDuration(context.CodePackageActivationContext); this.monitorServiceAddress = GetMonitorServiceAddress(context.CodePackageActivationContext); this.monitorServicePartitionKey = new ServicePartitionKey(this.myInfo.Id.Name); this.monitorCreationTask = new TaskCompletionSource <bool>(); }
private void Socket_OnFatalDisconnection(object sender, GatewayCloseCode e) { if (isDisposed) { return; } log.LogVerbose("Fatal disconnection occured, setting state to Disconnected."); state = GatewayState.Disconnected; (string message, ShardFailureReason reason) = GatewayCloseCodeToReason(e); gatewayFailureData = new GatewayFailureData(message, reason, null); handshakeCompleteEvent.Set(); OnFailure?.Invoke(this, gatewayFailureData); }
public async void Execute(Gateway gateway) { // 通信実装を毎フレーム呼び出し switch (State) { case GatewayState.GETTING_DREAMS_LIST: _existsFiles = FilerOperator.getExistsTextures(); _dream = await gateway.GetDreamsData(); if (_dream != null) { State = GatewayState.GETTING_DREAM_TEXTURE_INIT; } break; case GatewayState.GETTING_DREAM_TEXTURE_INIT: _image = _dream.images[_counter]; // 既にローカルにテクスチャが存在する場合はスキップ if (_existsFiles != null && _existsFiles.Contains(_image)) { State = GatewayState.GETTING_DREAM_TEXTURE_FINISHED; break; } //gateway.SetUrlFromFileName(_image); //State = gateway.GetDreamTexture(_image); break; case GatewayState.GETTING_DREAM_TEXTURE: //State = gateway.GetDreamTexture(_image); break; case GatewayState.GETTING_DREAM_TEXTURE_FINISHED: _counter++; if (_counter < _dream.images.Length) { State = GatewayState.GETTING_DREAM_TEXTURE_INIT; } else { State = GatewayState.GETTING_DREAM_TEXTURES_COMPLETED; } break; case GatewayState.GETTING_DREAM_TEXTURES_COMPLETED: break; } }
internal Gateway(string botToken, Shard shard, int totalShards) { this.botToken = botToken; this.shard = shard; this.totalShards = totalShards; http = new DiscordHttpClient(botToken); cache = shard.Cache; log = new DiscoreLogger($"Gateway#{shard.Id}"); state = GatewayState.Disconnected; handshakeCompleteEvent = new AsyncManualResetEvent(); handshakeCompleteCancellationSource = new CancellationTokenSource(); // Up-to-date rate limit parameters: https://discord.com/developers/docs/topics/gateway#rate-limiting identifyRateLimiter = new GatewayRateLimiter(5, 1); // 1 IDENTIFY per 5 seconds outboundPayloadRateLimiter = new GatewayRateLimiter(60, 120); // 120 outbound payloads every 60 seconds gameStatusUpdateRateLimiter = new GatewayRateLimiter(60, 5); // 5 status updates per minute InitializeDispatchHandlers(); }
public override async Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Started Discord Service"); _logger.LogDebug("Mod role: {ModRole}", _settings.Value.ModRole); _webSocket = new ClientWebSocket(); var url = (await _restApi.GetAsync <GetGatewayResponse>("/gateway", cancellationToken))?.Url ?? string.Empty; if (string.IsNullOrWhiteSpace(url)) { _logger.LogError("Unable to get Gateway URL"); return; } _logger.LogDebug("Got Gateway Url '{Url}'", url); _state = GatewayState.Connecting; _webSocket = new ClientWebSocket(); await _webSocket.ConnectAsync(new Uri($"{url}?v=9&encoding=json"), cancellationToken); await base.StartAsync(cancellationToken); }
private void SetGatewayState(GatewayState newState) { switch (newState) { case GatewayState.Disconnected: LogInfo("Disconnected."); break; case GatewayState.ConnectingToPort: LogInfo("Trying to connect..."); break; case GatewayState.ConnectingToGateway: LogInfo("Trying to communicate..."); break; case GatewayState.Connected: LogInfo("Gateway connected."); break; } if (gatewayState == GatewayState.Connected && newState != GatewayState.Connected) OnDisconnected?.Invoke(); else if (gatewayState != GatewayState.Connected && newState == GatewayState.Connected) OnConnected?.Invoke(); gatewayState = newState; OnGatewayStateChanged?.Invoke(gatewayState); }
/// <exception cref="OperationCanceledException"></exception> async Task ConnectLoop(bool resume, CancellationToken cancellationToken) { // Keep track of whether this is a resume or new session so // we can respond to the HELLO payload appropriately. isConnectionResuming = resume; log.LogVerbose($"[ConnectLoop] resume = {resume}"); handshakeCompleteEvent.Reset(); handshakeCompleteCancellationSource = new CancellationTokenSource(); while (!cancellationToken.IsCancellationRequested) { // Ensure previous socket has been closed if (socket != null) { UnsubscribeSocketEvents(); if (resume) { // Store previous sequence lastSequence = socket.Sequence; } if (socket.CanBeDisconnected) { log.LogVerbose("[ConnectLoop] Disconnecting previous socket..."); // If for some reason the socket cannot be disconnected gracefully, // DisconnectAsync will abort the socket after 5s. if (resume) { // Make sure to disconnect with a non 1000 code to ensure Discord doesn't // force us to make a new session since we are resuming. await socket.DisconnectAsync(WebSocketCloseStatus.EndpointUnavailable, "Reconnecting to resume...", cancellationToken).ConfigureAwait(false); } else { await socket.DisconnectAsync(WebSocketCloseStatus.NormalClosure, "Starting new session...", cancellationToken).ConfigureAwait(false); } } socket.Dispose(); } if (!resume) { // If not resuming, reset gateway session state. lastSequence = 0; } // Create a new socket socket = new GatewaySocket($"GatewaySocket#{shard.Id}", lastSequence, outboundPayloadRateLimiter, gameStatusUpdateRateLimiter, identifyRateLimiter); socket.OnHello = async() => { if (isDisposed) { return; } if (isConnectionResuming) { // Resume await socket.SendResumePayload(botToken, sessionId, lastSequence); } else { // Identify await socket.SendIdentifyPayload(botToken, lastShardStartConfig.GatewayLargeThreshold, shard.Id, totalShards); } }; SubscribeSocketEvents(); // Get the gateway URL if we don't have one string gatewayUrl = GatewayUrlMemoryCache.GatewayUrl; if (gatewayUrl == null) { try { log.LogVerbose("[ConnectLoop] Retrieving Gateway URL..."); gatewayUrl = await http.GetGateway().ConfigureAwait(false); GatewayUrlMemoryCache.UpdateUrl(gatewayUrl); } catch (Exception ex) when(ex is DiscordHttpApiException || ex is HttpRequestException || ex is OperationCanceledException) { log.LogError($"[ConnectLoop:GetGateway] {ex}"); log.LogError("[ConnectLoop] No gateway URL to connect with, trying again in 10s..."); await Task.Delay(10 * 1000, cancellationToken).ConfigureAwait(false); continue; } catch (Exception ex) { // This should never-ever happen, but we need to handle it just in-case. log.LogError(ex); log.LogError("[ConnectLoop] Uncaught severe error occured while getting the Gateway URL, setting state to Disconnected."); state = GatewayState.Disconnected; gatewayFailureData = new GatewayFailureData( "Failed to retrieve the Gateway URL because of an unknown error.", ShardFailureReason.Unknown, ex); handshakeCompleteEvent.Set(); OnFailure?.Invoke(this, gatewayFailureData); break; } } log.LogVerbose($"[ConnectLoop] gatewayUrl = {gatewayUrl}"); // Wait if necessary if (nextConnectionDelayMs > 0) { log.LogVerbose($"[ConnectLoop] Waiting {nextConnectionDelayMs}ms before connecting socket..."); await Task.Delay(nextConnectionDelayMs, cancellationToken).ConfigureAwait(false); nextConnectionDelayMs = 0; } try { // Attempt to connect await socket.ConnectAsync(new Uri($"{gatewayUrl}?v={GATEWAY_VERSION}&encoding=json"), cancellationToken) .ConfigureAwait(false); // At this point the socket has successfully connected log.LogVerbose("[ConnectLoop] Socket connected successfully."); break; } catch (WebSocketException wsex) { UnsubscribeSocketEvents(); // Failed to connect log.LogError("[ConnectLoop] Failed to connect: " + $"{wsex.WebSocketErrorCode} ({(int)wsex.WebSocketErrorCode}), {wsex.Message}"); // Invalidate the cached URL since we failed to connect the socket log.LogVerbose("[ConnectLoop] Invalidating Gateway URL..."); GatewayUrlMemoryCache.InvalidateUrl(); // Wait 5s then retry log.LogVerbose("[ConnectLoop] Waiting 5s before retrying..."); await Task.Delay(5000, cancellationToken).ConfigureAwait(false); } } // If the token is cancelled between the socket successfully connecting and the loop exiting, // do not throw an exception because the connection did technically complete before the cancel. if (socket == null || !socket.IsConnected) { // If the loop stopped from the token being cancelled, ensure an exception is still thrown. cancellationToken.ThrowIfCancellationRequested(); } // If this is an automatic reconnection, fire OnReconnected event if (state == GatewayState.Connected) { if (resume) { log.LogInfo("[ConnectLoop:Reconnection] Successfully started a resume."); } else { log.LogInfo("[ConnectLoop:Reconnection] Successfully started creating a new session."); } OnReconnected?.Invoke(this, new GatewayReconnectedEventArgs(!resume)); } }
public void Connect(Action <string> onIdentify, Action <string, SocketResponseDataBase> onMessage, bool reconnect = false) { if (this.m_ReadyState != GatewayState.CLOSED && this.m_ReadyState != GatewayState.Init) { return; } if (!reconnect) { this.m_OnIdentify = onIdentify; this.m_OnMessage = onMessage; this.m_CloseRequired = false; } else { DebugerUtils.DebugAssert(this.m_OnIdentify != null, "fatal error: reconnect before initial connect !"); DebugerUtils.DebugAssert(this.m_OnMessage != null, "fatal error: reconnect before initial connect !"); } this.m_ReadyState = GatewayState.CONNECTING; this.m_BackOff.Cancel(); this._SelectGateway( createWebSocketFunc: url => { if (url != null) { this._Reset(isInit: true); this.m_Socket?.Close(); this.m_Socket = null; this.m_Socket = new WebSocket(this.m_Host, this.m_SslHandShakeError); this.m_Socket.Connect(url, OnError: msg => { this._OnClose(1); }, OnClose: this._OnClose, OnConnected: () => { NetworkStatusManager.isConnected = true; DebugerUtils.DebugAssert(this.m_CommitId != null, "fatal error: commit Id is not correctly set before connection!"); this.m_ReadyState = GatewayState.OPEN; if (!this._Resume()) { this.m_OnIdentify.Invoke(this.m_CommitId); } this._StartHeartBeat(); }, OnMessage: bytes => { NetworkStatusManager.isConnected = true; string type = ""; var data = this._OnMessage(bytes, ref type); if (data != null) { this.m_OnMessage?.Invoke(type, data); } } ); } else { NetworkStatusManager.isConnected = false; var delay = this._OnFail(); DebugerUtils.DebugAssert(false, $"gateway discovery failed, retry in {delay / 1000f} seconds"); } }); }
/// <exception cref="GatewayHandshakeException"></exception> /// <exception cref="InvalidOperationException"></exception> /// <exception cref="ObjectDisposedException"></exception> public async Task ConnectAsync(ShardStartConfig config, CancellationToken cancellationToken) { if (isDisposed) { throw new ObjectDisposedException(GetType().FullName); } if (state == GatewayState.Connected) { throw new InvalidOperationException("The gateway is already connected!"); } if (state == GatewayState.Connecting) { throw new InvalidOperationException("The gateway is already connecting!"); } // Begin connecting state = GatewayState.Connecting; lastShardStartConfig = config; gatewayFailureData = null; handshakeCompleteEvent.Reset(); connectTask = ConnectLoop(false, cancellationToken); try { // Connect socket await connectTask.ConfigureAwait(false); try { // Wait for the handshake to complete await handshakeCompleteEvent.WaitAsync(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { // Since this was cancelled after the socket connected, // we need to do a full disconnect. await FullDisconnect(); throw; } // Check for errors if (gatewayFailureData != null) { throw new GatewayHandshakeException(gatewayFailureData); } // Connection successful log.LogVerbose("[ConnectAsync] Setting state to Connected."); state = GatewayState.Connected; } catch { // Reset to disconnected if cancelled or failed log.LogVerbose("[ConnectAsync] Setting state to Disconnected."); state = GatewayState.Disconnected; throw; } }
private async Task HandlePacket(string json, CancellationToken cancellationToken) { var genericGateway = JsonSerializer.Deserialize <GatewayPacket <object> >(json, _jsonOptions); if (genericGateway is null) { _logger.LogError("Unable to parse generic gateway packet"); return; } if (genericGateway.SequenceNumber is not null) { _lastSequenceNumber = genericGateway.SequenceNumber; } switch (_state) { case GatewayState.Connecting: if (genericGateway.Opcode == Opcode.Hello) { // We got a Hello, start sending heartbeats! var helloPacket = DeserializePacket <HelloData>(json); if (helloPacket is null || helloPacket.Data is null) { _logger.LogError("Invalid hello packet"); return; } _heartbeatInterval = TimeSpan.FromMilliseconds(helloPacket.Data.HeartbeatInterval); _logger.LogDebug("Heartbeat interval set to '{Interval}'", _heartbeatInterval); if (_heartbeatTask is null) { _heartbeatTask = new BackgroundTask(SendHeartbeatAsync); } // Queue up an Identify packet var identifyPacket = new GatewayPacket <IdentifyData> { Data = new IdentifyData { // TODO: Intents Intents = 1, Token = _settings.Value.Token, Properties = new IdentifyDataProperties { Browser = "octantis", Device = "octantis", Os = "linux" } }, Opcode = Opcode.Identify }; await TransmitGatewayAsync(identifyPacket, cancellationToken); } else if (genericGateway.Opcode == Opcode.Dispatch && genericGateway.EventName == Events.Ready) { var readyPacket = DeserializePacket <ReadyData>(json); if (readyPacket is null || readyPacket.Data is null) { _logger.LogError("Invalid ready packet"); return; } ApplicationId = readyPacket.Data.User?.Id ?? 0; _sessionId = readyPacket.Data.SessionId; _logger.LogInformation("Connected to gateway v{Version}, application id '{Id}', session id '{Id}'", readyPacket.Data.GatewayVersion, ApplicationId, _sessionId); foreach (var guild in readyPacket.Data.Guilds) { _logger.LogDebug("Guild '{Id}'", guild.Id); } // Got the ready event, move up to connected state! _state = GatewayState.Connected; } else { _logger.LogWarning("Unhandled opcode '{Opcode}'", genericGateway.Opcode); } break; case GatewayState.Connected: { // Handle events if (genericGateway.Opcode == Opcode.Heartbeat) { // Force heartbeat send? _logger.LogError("TODO: SEND HEARTBEAT"); } else if (genericGateway.Opcode == Opcode.Dispatch) { // EVENTS :D var type = Events.FromString(genericGateway.EventName !); List <Registration>?registrationsForEvent; lock (_registrations) { _registrations.TryGetValue(type, out registrationsForEvent); } if (registrationsForEvent is not null) { foreach (var registration in registrationsForEvent) { var packetType = typeof(GatewayPacket <>).MakeGenericType(registration.ArgumentType); dynamic deserializedAsType = JsonSerializer.Deserialize(json, packetType, _jsonOptions) !; registration.Action(deserializedAsType.Data); } } else { _logger.LogWarning("Unhandled event type '{Type}'", type); } } } break; } }