From 5215a2da2ecad5a6d51103f9d1220fe0eea5444c Mon Sep 17 00:00:00 2001 From: Kyle Baran Date: Thu, 22 Sep 2022 17:18:33 -0700 Subject: [PATCH] Added error handling for instance provisioning returning a shutting-down instance There are corner cases where an instance-provision occurs right before an instanceserver shuts down, or has its shutdown status recorded in the database. The provision returns an IP address that is immediately invalid. The SocketWebRTCClientNetwork instantiation was not handling this case, and throwing errors. Now, whether there's an error with creating the websocket connection, or it fails to connect within 3 seconds, it disconnects and then attempts another instance-provision. --- .../transports/SocketWebRTCClientNetwork.ts | 91 ++++++++++++++++--- 1 file changed, 77 insertions(+), 14 deletions(-) diff --git a/packages/client-core/src/transports/SocketWebRTCClientNetwork.ts b/packages/client-core/src/transports/SocketWebRTCClientNetwork.ts index 06f6cf82d3..9c486909d1 100755 --- a/packages/client-core/src/transports/SocketWebRTCClientNetwork.ts +++ b/packages/client-core/src/transports/SocketWebRTCClientNetwork.ts @@ -2,14 +2,27 @@ import * as mediasoupClient from 'mediasoup-client' import { Consumer, DataProducer, Transport as MediaSoupTransport, Producer } from 'mediasoup-client/lib/types' import { io as ioclient, Socket } from 'socket.io-client' +import { Channel } from '@xrengine/common/src/interfaces/Channel' import { UserId } from '@xrengine/common/src/interfaces/UserId' import multiLogger from '@xrengine/common/src/logger' import { Engine } from '@xrengine/engine/src/ecs/classes/Engine' import { Network } from '@xrengine/engine/src/networking/classes/Network' import { MessageTypes } from '@xrengine/engine/src/networking/enums/MessageTypes' -import { clearOutgoingActions } from '@xrengine/hyperflux' +import { clearOutgoingActions, dispatchAction } from '@xrengine/hyperflux' import ActionFunctions, { Action, Topic } from '@xrengine/hyperflux/functions/ActionFunctions' +import { + accessLocationInstanceConnectionState, + LocationInstanceConnectionAction, + LocationInstanceConnectionService +} from '../common/services/LocationInstanceConnectionService' +import { + accessMediaInstanceConnectionState, + MediaInstanceConnectionAction, + MediaInstanceConnectionService +} from '../common/services/MediaInstanceConnectionService' +import { accessChatState } from '../social/services/ChatService' +import { accessLocationState } from '../social/services/LocationService' import { accessAuthState } from '../user/services/AuthService' import { instanceserverHost } from '../util/config' import { onConnectToInstance } from './SocketWebRTCClientFunctions' @@ -25,6 +38,46 @@ const promisedRequest = (socket: Socket) => { } } +const handleFailedConnection = (locationConnectionFailed) => { + if (locationConnectionFailed) { + const currentLocation = accessLocationState().currentLocation.location + const locationInstanceConnectionState = accessLocationInstanceConnectionState() + const instanceId = Engine.instance.currentWorld._worldHostId + if (!locationInstanceConnectionState.instances[instanceId].connected.value) { + dispatchAction(LocationInstanceConnectionAction.disconnect({ instanceId })) + LocationInstanceConnectionService.provisionServer( + currentLocation.id.value, + instanceId || undefined, + currentLocation.sceneId.value + ) + } + } else { + const mediaInstanceConnectionState = accessMediaInstanceConnectionState() + const instanceId = Engine.instance.currentWorld._mediaHostId + if (!mediaInstanceConnectionState.instances[instanceId].connected.value) { + dispatchAction(MediaInstanceConnectionAction.disconnect({ instanceId })) + const authState = accessAuthState() + const selfUser = authState.user + const chatState = accessChatState() + const channelState = chatState.channels + const channels = channelState.channels.value as Channel[] + const channelEntries = Object.values(channels).filter((channel) => !!channel) as any + const instanceChannel = channelEntries.find( + (entry) => entry.instanceId === Engine.instance.currentWorld.worldNetwork?.hostId + ) + if (instanceChannel) { + MediaInstanceConnectionService.provisionServer(instanceChannel?.id!, true) + } else { + const partyChannel = Object.values(chatState.channels.channels.value).find( + (channel) => channel.channelType === 'party' && channel.partyId === selfUser.partyId.value + ) + MediaInstanceConnectionService.provisionServer(partyChannel?.id!, false) + } + } + } + return +} + export class SocketWebRTCClientNetwork extends Network { constructor(hostId: UserId, topic: Topic) { super(hostId, topic) @@ -96,23 +149,33 @@ export class SocketWebRTCClientNetwork extends Network { if (locationId) delete query.channelId if (channelId) delete query.locationId - if (globalThis.process.env['VITE_LOCAL_BUILD'] === 'true') { - this.socket = ioclient(`https://${ipAddress as string}:${port.toString()}`, { - query - }) - } else if (process.env.APP_ENV === 'development') { - this.socket = ioclient(`${ipAddress as string}:${port.toString()}`, { - query - }) - } else { - this.socket = ioclient(instanceserverHost, { - path: `/socket.io/${ipAddress as string}/${port.toString()}`, - query - }) + try { + if (globalThis.process.env['VITE_LOCAL_BUILD'] === 'true') { + this.socket = ioclient(`https://${ipAddress as string}:${port.toString()}`, { + query + }) + } else if (process.env.APP_ENV === 'development') { + this.socket = ioclient(`${ipAddress as string}:${port.toString()}`, { + query + }) + } else { + this.socket = ioclient(instanceserverHost, { + path: `/socket.io/${ipAddress as string}/${port.toString()}`, + query + }) + } + } catch (err) { + logger.error(err) + return handleFailedConnection(locationId != null) } this.request = promisedRequest(this.socket) + const connectionFailTimeout = setTimeout(() => { + return handleFailedConnection(locationId != null) + }, 3000) + this.socket.on('connect', () => { + clearTimeout(connectionFailTimeout) if (this.reconnecting) { this.reconnecting = false ;(this.socket as any)._connected = false