From dc9240218fde59160b0766bc83d25c108c0acd3a Mon Sep 17 00:00:00 2001
From: Andrey Sobolev <haiodo@users.noreply.github.com>
Date: Sat, 11 Jan 2025 21:53:04 +0700
Subject: [PATCH] QFIX: Restore services work (#7641)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
---
 common/scripts/version.txt                    |  2 +-
 dev/docker-compose.yaml                       |  4 +-
 plugins/client-resources/src/connection.ts    | 26 +++++-
 server/server/src/client.ts                   |  2 +-
 server/server/src/sessionManager.ts           | 82 ++++++++++---------
 .../pod-calendar/src/calendarController.ts    | 14 +++-
 services/github/pod-github/src/client.ts      |  2 +
 services/github/pod-github/src/platform.ts    | 20 +++--
 .../gmail/pod-gmail/src/gmailController.ts    | 14 +++-
 9 files changed, 109 insertions(+), 57 deletions(-)

diff --git a/common/scripts/version.txt b/common/scripts/version.txt
index bda3e86cd2..0f86ae7db6 100644
--- a/common/scripts/version.txt
+++ b/common/scripts/version.txt
@@ -1 +1 @@
-"0.6.287"
\ No newline at end of file
+"0.6.382"
\ No newline at end of file
diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml
index d3f260117c..5aa9e2777c 100644
--- a/dev/docker-compose.yaml
+++ b/dev/docker-compose.yaml
@@ -270,7 +270,7 @@ services:
       - UV_THREADPOOL_SIZE=10
       - SERVER_PORT=3333
       - SERVER_SECRET=secret
-      - ENABLE_COMPRESSION=true
+      - ENABLE_COMPRESSION=false
       - STATS_URL=http://host.docker.internal:4900
       - FULLTEXT_URL=http://host.docker.internal:4700
       # - DB_URL=postgresql://postgres:example@postgres:5432
@@ -343,7 +343,7 @@ services:
       # - UV_THREADPOOL_SIZE=10
       - SERVER_PORT=3332
       - SERVER_SECRET=secret
-      - ENABLE_COMPRESSION=true
+      - ENABLE_COMPRESSION=false
       - FULLTEXT_URL=http://host.docker.internal:4702
       - STATS_URL=http://host.docker.internal:4900
       - DB_URL=postgresql://root@host.docker.internal:26257/defaultdb?sslmode=disable
diff --git a/plugins/client-resources/src/connection.ts b/plugins/client-resources/src/connection.ts
index 9f9f5ae960..2d31a789bf 100644
--- a/plugins/client-resources/src/connection.ts
+++ b/plugins/client-resources/src/connection.ts
@@ -513,8 +513,17 @@ class Connection implements ClientConnection {
               console.error(err)
             }
           }
-          const resp = this.rpcHandler.readResponse<any>(data, this.binaryMode)
-          this.handleMsg(socketId, resp)
+          try {
+            const resp = this.rpcHandler.readResponse<any>(data, this.binaryMode)
+            this.handleMsg(socketId, resp)
+          } catch (err: any) {
+            if (!this.helloReceived) {
+              // Just error and ignore for now.
+              console.error(err)
+            } else {
+              throw err
+            }
+          }
         })
       } else {
         let data = event.data
@@ -526,8 +535,17 @@ class Connection implements ClientConnection {
             console.error(err)
           }
         }
-        const resp = this.rpcHandler.readResponse<any>(data, this.binaryMode)
-        this.handleMsg(socketId, resp)
+        try {
+          const resp = this.rpcHandler.readResponse<any>(data, this.binaryMode)
+          this.handleMsg(socketId, resp)
+        } catch (err: any) {
+          if (!this.helloReceived) {
+            // Just error and ignore for now.
+            console.error(err)
+          } else {
+            throw err
+          }
+        }
       }
     }
     wsocket.onclose = (ev) => {
diff --git a/server/server/src/client.ts b/server/server/src/client.ts
index 6d1c825e62..b867415df4 100644
--- a/server/server/src/client.ts
+++ b/server/server/src/client.ts
@@ -58,7 +58,7 @@ export class ClientSession implements Session {
   createTime = Date.now()
   requests = new Map<string, SessionRequest>()
   binaryMode: boolean = false
-  useCompression: boolean = true
+  useCompression: boolean = false
   sessionId = ''
   lastRequest = Date.now()
 
diff --git a/server/server/src/sessionManager.ts b/server/server/src/sessionManager.ts
index 64a9124cb6..9686203d69 100644
--- a/server/server/src/sessionManager.ts
+++ b/server/server/src/sessionManager.ts
@@ -498,12 +498,6 @@ class TSessionManager implements SessionManager {
       workspace.workspaceInitCompleted = true
     }
 
-    // We do not need to wait for set-status, just return session to client
-    const _workspace = workspace
-    void ctx
-      .with('set-status', {}, (ctx) => this.trySetStatus(ctx, pipeline, session, true, _workspace.workspaceId))
-      .catch(() => {})
-
     if (this.timeMinutes > 0) {
       ws.send(ctx, { result: this.createMaintenanceWarning() }, session.binaryMode, session.useCompression)
     }
@@ -837,7 +831,7 @@ class TSessionManager implements SessionManager {
       s.workspaceClosed = true
       if (reason === 'upgrade' || reason === 'force-close') {
         // Override message handler, to wait for upgrading response from clients.
-        this.sendUpgrade(workspace.context, webSocket, s.binaryMode)
+        this.sendUpgrade(workspace.context, webSocket, s.binaryMode, s.useCompression)
       }
       webSocket.close()
       this.reconnectIds.delete(s.sessionId)
@@ -877,7 +871,7 @@ class TSessionManager implements SessionManager {
     }
   }
 
-  private sendUpgrade (ctx: MeasureContext, webSocket: ConnectionSocket, binary: boolean): void {
+  private sendUpgrade (ctx: MeasureContext, webSocket: ConnectionSocket, binary: boolean, compression: boolean): void {
     webSocket.send(
       ctx,
       {
@@ -886,7 +880,7 @@ class TSessionManager implements SessionManager {
         }
       },
       binary,
-      false
+      compression
     )
   }
 
@@ -1081,39 +1075,49 @@ class TSessionManager implements SessionManager {
     ws: ConnectionSocket,
     requestCtx: MeasureContext<any>
   ): Promise<void> {
-    const hello = request as HelloRequest
-    service.binaryMode = hello.binary ?? false
-    service.useCompression = this.enableCompression ? hello.compression ?? false : false
+    try {
+      const hello = request as HelloRequest
+      service.binaryMode = hello.binary ?? false
+      service.useCompression = this.enableCompression ? hello.compression ?? false : false
 
-    if (LOGGING_ENABLED) {
-      ctx.info('hello happen', {
-        workspace,
-        user: service.getUser(),
+      if (LOGGING_ENABLED) {
+        ctx.info('hello happen', {
+          workspace,
+          user: service.getUser(),
+          binary: service.binaryMode,
+          compression: service.useCompression,
+          timeToHello: Date.now() - service.createTime,
+          workspaceUsers: this.workspaces.get(workspace)?.sessions?.size,
+          totalUsers: this.sessions.size
+        })
+      }
+      const reconnect = this.reconnectIds.has(service.sessionId)
+      if (reconnect) {
+        this.reconnectIds.delete(service.sessionId)
+      }
+      const pipeline =
+        service.workspace.pipeline instanceof Promise ? await service.workspace.pipeline : service.workspace.pipeline
+      const helloResponse: HelloResponse = {
+        id: -1,
+        result: 'hello',
         binary: service.binaryMode,
-        compression: service.useCompression,
-        timeToHello: Date.now() - service.createTime,
-        workspaceUsers: this.workspaces.get(workspace)?.sessions?.size,
-        totalUsers: this.sessions.size
-      })
+        reconnect,
+        serverVersion: this.serverVersion,
+        lastTx: pipeline.context.lastTx,
+        lastHash: pipeline.context.lastHash,
+        account: service.getRawAccount(pipeline),
+        useCompression: service.useCompression
+      }
+      ws.send(requestCtx, helloResponse, false, false)
+
+      // We do not need to wait for set-status, just return session to client
+      const _workspace = service.workspace
+      void ctx
+        .with('set-status', {}, (ctx) => this.trySetStatus(ctx, pipeline, service, true, _workspace.workspaceId))
+        .catch(() => {})
+    } catch (err: any) {
+      ctx.error('error', { err })
     }
-    const reconnect = this.reconnectIds.has(service.sessionId)
-    if (reconnect) {
-      this.reconnectIds.delete(service.sessionId)
-    }
-    const pipeline =
-      service.workspace.pipeline instanceof Promise ? await service.workspace.pipeline : service.workspace.pipeline
-    const helloResponse: HelloResponse = {
-      id: -1,
-      result: 'hello',
-      binary: service.binaryMode,
-      reconnect,
-      serverVersion: this.serverVersion,
-      lastTx: pipeline.context.lastTx,
-      lastHash: pipeline.context.lastHash,
-      account: service.getRawAccount(pipeline),
-      useCompression: service.useCompression
-    }
-    ws.send(requestCtx, helloResponse, false, false)
   }
 }
 
diff --git a/services/calendar/pod-calendar/src/calendarController.ts b/services/calendar/pod-calendar/src/calendarController.ts
index b929ee4204..ad6783d4c9 100644
--- a/services/calendar/pod-calendar/src/calendarController.ts
+++ b/services/calendar/pod-calendar/src/calendarController.ts
@@ -13,12 +13,14 @@
 // limitations under the License.
 //
 
-import { Account, RateLimiter, Ref } from '@hcengineering/core'
+import { Account, isActiveMode, RateLimiter, Ref, systemAccountEmail } from '@hcengineering/core'
 import { type Db } from 'mongodb'
 import { type CalendarClient } from './calendar'
 import config from './config'
 import { type ProjectCredentials, type Token, type User } from './types'
 import { WorkspaceClient } from './workspaceClient'
+import { getWorkspaceInfo } from '@hcengineering/server-client'
+import { generateToken } from '@hcengineering/server-token'
 
 export class CalendarController {
   private readonly workspaces: Map<string, WorkspaceClient> = new Map<string, WorkspaceClient>()
@@ -60,6 +62,16 @@ export class CalendarController {
 
     for (const [workspace, tokens] of groups) {
       await limiter.add(async () => {
+        const wstok = generateToken(systemAccountEmail, { name: workspace })
+        const info = await getWorkspaceInfo(wstok)
+        if (info === undefined) {
+          console.log('workspace not found', workspace)
+          return
+        }
+        if (!isActiveMode(info.mode)) {
+          console.log('workspace is not active', workspace)
+          return
+        }
         const startPromise = this.startWorkspace(workspace, tokens)
         const timeoutPromise = new Promise<void>((resolve) => {
           setTimeout(() => {
diff --git a/services/github/pod-github/src/client.ts b/services/github/pod-github/src/client.ts
index 9093844c54..c8bed1b8b1 100644
--- a/services/github/pod-github/src/client.ts
+++ b/services/github/pod-github/src/client.ts
@@ -36,6 +36,8 @@ export async function createPlatformClient (
     },
     { mode: 'github' }
   )
+  setMetadata(client.metadata.UseBinaryProtocol, true)
+  setMetadata(client.metadata.UseProtocolCompression, true)
   setMetadata(client.metadata.ConnectionTimeout, timeout)
   setMetadata(client.metadata.FilterModel, 'client')
   const endpoint = await getTransactorEndpoint(token)
diff --git a/services/github/pod-github/src/platform.ts b/services/github/pod-github/src/platform.ts
index d10e7319db..25f1307033 100644
--- a/services/github/pod-github/src/platform.ts
+++ b/services/github/pod-github/src/platform.ts
@@ -795,6 +795,7 @@ export class PlatformWorker {
             total: workspaces.length
           })
 
+          let initialized = false
           const worker = await GithubWorker.create(
             this,
             workerCtx,
@@ -811,17 +812,20 @@ export class PlatformWorker {
               if (event === ClientConnectEvent.Refresh || event === ClientConnectEvent.Upgraded) {
                 void this.clients.get(workspace)?.refreshClient(event === ClientConnectEvent.Upgraded)
               }
-              // We need to check if workspace is inactive
-              void this.checkWorkspaceIsActive(token, workspace).then((res) => {
-                if (res === undefined) {
-                  this.ctx.warn('Workspace is inactive, removing from clients list.', { workspace })
-                  this.clients.delete(workspace)
-                  void worker?.close()
-                }
-              })
+              if (initialized) {
+                // We need to check if workspace is inactive
+                void this.checkWorkspaceIsActive(token, workspace).then((res) => {
+                  if (res === undefined) {
+                    this.ctx.warn('Workspace is inactive, removing from clients list.', { workspace })
+                    this.clients.delete(workspace)
+                    void worker?.close()
+                  }
+                })
+              }
             }
           )
           if (worker !== undefined) {
+            initialized = true
             workerCtx.info('************************* Register worker Done ************************* ', {
               workspaceId: workspaceInfo.workspaceId,
               workspace: workspaceInfo.workspace,
diff --git a/services/gmail/pod-gmail/src/gmailController.ts b/services/gmail/pod-gmail/src/gmailController.ts
index 8be43cdd5c..5c0a98fb60 100644
--- a/services/gmail/pod-gmail/src/gmailController.ts
+++ b/services/gmail/pod-gmail/src/gmailController.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 //
 
-import { MeasureContext, RateLimiter } from '@hcengineering/core'
+import { isActiveMode, MeasureContext, RateLimiter, systemAccountEmail } from '@hcengineering/core'
 import type { StorageAdapter } from '@hcengineering/server-core'
 
 import { type Db } from 'mongodb'
@@ -22,6 +22,8 @@ import config from './config'
 import { type GmailClient } from './gmail'
 import { type ProjectCredentials, type Token, type User } from './types'
 import { WorkspaceClient } from './workspaceClient'
+import { generateToken } from '@hcengineering/server-token'
+import { getWorkspaceInfo } from '@hcengineering/server-client'
 
 export class GmailController {
   private readonly workspaces: Map<string, WorkspaceClient> = new Map<string, WorkspaceClient>()
@@ -72,6 +74,16 @@ export class GmailController {
     const limiter = new RateLimiter(config.InitLimit)
     for (const [workspace, tokens] of groups) {
       await limiter.add(async () => {
+        const wstok = generateToken(systemAccountEmail, { name: workspace })
+        const info = await getWorkspaceInfo(wstok)
+        if (info === undefined) {
+          console.log('workspace not found', workspace)
+          return
+        }
+        if (!isActiveMode(info.mode)) {
+          console.log('workspace is not active', workspace)
+          return
+        }
         const startPromise = this.startWorkspace(workspace, tokens)
         const timeoutPromise = new Promise<void>((resolve) => {
           setTimeout(() => {