From b3969d1fd7d2db053831b479a73c6eb4b636edc6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 12:51:06 +0000 Subject: [PATCH 2/5] Optimize server rotation to use primary server first, only rotate on network errors Co-authored-by: Sunwuyuan <88357633+Sunwuyuan@users.noreply.github.com> --- src/utils/providers/kvServerProvider.js | 22 ++++---- src/utils/serverRotation.js | 74 ++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 13 deletions(-) diff --git a/src/utils/providers/kvServerProvider.js b/src/utils/providers/kvServerProvider.js index 93952d5..1b5f9d9 100644 --- a/src/utils/providers/kvServerProvider.js +++ b/src/utils/providers/kvServerProvider.js @@ -1,7 +1,7 @@ import axios from "@/axios/axios"; import {formatResponse, formatError} from "../dataProvider"; import {getSetting} from "../settings"; -import {tryWithRotation, isRotationEnabled} from "../serverRotation"; +import {tryWithPrimaryServer, isRotationEnabled} from "../serverRotation"; // Helper function to get request headers with kvtoken const getHeaders = () => { @@ -23,9 +23,9 @@ const getHeaders = () => { export const kvServerProvider = { async loadNamespaceInfo() { try { - // Use rotation for classworkscloud provider + // Use primary server with fallback for classworkscloud provider if (isRotationEnabled()) { - return await tryWithRotation(async (serverUrl) => { + return await tryWithPrimaryServer(async (serverUrl) => { const res = await axios.get(`${serverUrl}/kv/_info`, { headers: getHeaders(), }); @@ -52,9 +52,9 @@ export const kvServerProvider = { async updateNamespaceInfo(data) { try { - // Use rotation for classworkscloud provider + // Use primary server with fallback for classworkscloud provider if (isRotationEnabled()) { - return await tryWithRotation(async (serverUrl) => { + return await tryWithPrimaryServer(async (serverUrl) => { const res = await axios.put(`${serverUrl}/kv/_info`, data, { headers: getHeaders(), }); @@ -78,9 +78,9 @@ export const kvServerProvider = { async loadData(key) { try { - // Use rotation for classworkscloud provider + // Use primary server with fallback for classworkscloud provider if (isRotationEnabled()) { - return await tryWithRotation(async (serverUrl) => { + return await tryWithPrimaryServer(async (serverUrl) => { const res = await axios.get(`${serverUrl}/kv/${key}`, { headers: getHeaders(), }); @@ -108,9 +108,9 @@ export const kvServerProvider = { async saveData(key, data) { try { - // Use rotation for classworkscloud provider + // Use primary server with fallback for classworkscloud provider if (isRotationEnabled()) { - return await tryWithRotation(async (serverUrl) => { + return await tryWithPrimaryServer(async (serverUrl) => { await axios.post(`${serverUrl}/kv/${key}`, data, { headers: getHeaders(), }); @@ -171,9 +171,9 @@ export const kvServerProvider = { skip: skip.toString() }); - // Use rotation for classworkscloud provider + // Use primary server with fallback for classworkscloud provider if (isRotationEnabled()) { - return await tryWithRotation(async (serverUrl) => { + return await tryWithPrimaryServer(async (serverUrl) => { const res = await axios.get(`${serverUrl}/kv/_keys?${params}`, { headers: getHeaders(), }); diff --git a/src/utils/serverRotation.js b/src/utils/serverRotation.js index ee0c286..a0717f2 100644 --- a/src/utils/serverRotation.js +++ b/src/utils/serverRotation.js @@ -11,6 +11,9 @@ const CLASSWORKS_CLOUD_SERVERS = [ "https://kv-service.wuyuan.dev", ]; +// Track the current primary server (the one that's currently working) +let primaryServerUrl = null; + /** * Get the list of servers to try for the given provider * @param {string} provider - The provider type @@ -59,6 +62,11 @@ export async function tryWithRotation(operation, options = {}) { onServerTried({ url: serverUrl, status: "success", tried: [...triedServers] }); } + // Update primary server on success (for classworkscloud provider) + if (provider === "classworkscloud") { + primaryServerUrl = serverUrl; + } + return result; } catch (error) { lastError = error; @@ -82,7 +90,7 @@ export async function tryWithRotation(operation, options = {}) { /** * Get the effective server URL for the current provider - * For classworkscloud, returns the first server in the list + * For classworkscloud, returns the primary server (last known working) or first server in the list * For other providers, returns the configured domain * @returns {string} Server URL */ @@ -90,7 +98,8 @@ export function getEffectiveServerUrl() { const provider = getSetting("server.provider"); if (provider === "classworkscloud") { - return CLASSWORKS_CLOUD_SERVERS[0]; + // Return primary server if available, otherwise first in list + return primaryServerUrl || CLASSWORKS_CLOUD_SERVERS[0]; } return getSetting("server.domain") || ""; @@ -104,3 +113,64 @@ export function isRotationEnabled() { const provider = getSetting("server.provider"); return provider === "classworkscloud"; } + +/** + * Check if an error is a network error that should trigger server rotation + * @param {Error} error - The error to check + * @returns {boolean} + */ +function isNetworkError(error) { + // Network errors from axios typically have no response or specific error codes + if (!error.response) { + return true; // No response = network issue + } + + // Server timeout or connection errors + if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT' || + error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') { + return true; + } + + // 5xx errors might indicate server issues worth retrying + const status = error.response?.status; + if (status >= 500) { + return true; + } + + return false; +} + +/** + * Try operation with primary server first, fallback to rotation on network errors only + * This is more efficient than always trying rotation for every request + * @param {Function} operation - Async function that takes a serverUrl and returns a promise + * @param {Object} options - Options + * @param {string} options.provider - Provider type (optional, defaults to current setting) + * @returns {Promise} Result from the operation + */ +export async function tryWithPrimaryServer(operation, options = {}) { + const provider = options.provider || getSetting("server.provider"); + + // For non-classworkscloud providers, just use the configured domain + if (provider !== "classworkscloud") { + const serverUrl = getSetting("server.domain"); + return await operation(serverUrl); + } + + // For classworkscloud, try primary server first + const primaryUrl = getEffectiveServerUrl(); + + try { + return await operation(primaryUrl); + } catch (error) { + // Only rotate to other servers if it's a network error + if (isNetworkError(error)) { + console.warn(`Primary server ${primaryUrl} failed with network error, trying rotation...`); + // Use full rotation, which will update the primary server if a different one succeeds + return await tryWithRotation(operation, options); + } + + // For non-network errors (e.g., 404, 401, validation errors), don't retry with other servers + throw error; + } +} From 45c48cdf82b3a1b16dcfc267d7b3670b90cb126d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 12:52:55 +0000 Subject: [PATCH 3/5] Update api.js to use tryWithPrimaryServer and clean up unused imports Co-authored-by: Sunwuyuan <88357633+Sunwuyuan@users.noreply.github.com> --- src/utils/api.js | 6 +++--- src/utils/socketClient.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/api.js b/src/utils/api.js index d4847a2..370e0b5 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -1,6 +1,6 @@ import axios from "@/axios/axios"; import {getSetting} from "@/utils/settings"; -import {tryWithRotation, isRotationEnabled} from "@/utils/serverRotation"; +import {tryWithPrimaryServer, isRotationEnabled} from "@/utils/serverRotation"; // Helper function to check if provider is valid for API calls const isValidProvider = () => { @@ -35,9 +35,9 @@ export const getNamespaceInfo = async () => { } try { - // Use rotation for classworkscloud provider + // Use primary server with fallback for classworkscloud provider if (isRotationEnabled()) { - const response = await tryWithRotation(async (serverUrl) => { + const response = await tryWithPrimaryServer(async (serverUrl) => { return await axios.get(`${serverUrl}/kv/_info`, { headers: getHeaders(), }); diff --git a/src/utils/socketClient.js b/src/utils/socketClient.js index 5f86814..f2391f4 100644 --- a/src/utils/socketClient.js +++ b/src/utils/socketClient.js @@ -4,7 +4,7 @@ import {io} from 'socket.io-client'; import {getSetting} from '@/utils/settings'; -import {getEffectiveServerUrl, isRotationEnabled, tryWithRotation} from '@/utils/serverRotation'; +import {getEffectiveServerUrl, isRotationEnabled} from '@/utils/serverRotation'; let socket = null; let connectedDomain = null; From f56c9b558e3e46a74e48e493707c76aa75e65d02 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:07:22 +0000 Subject: [PATCH 4/5] Simplify server rotation to retry on any error, not just network errors Co-authored-by: Sunwuyuan <88357633+Sunwuyuan@users.noreply.github.com> --- src/utils/serverRotation.js | 46 +++++++------------------------------ 1 file changed, 8 insertions(+), 38 deletions(-) diff --git a/src/utils/serverRotation.js b/src/utils/serverRotation.js index a0717f2..cc1aa15 100644 --- a/src/utils/serverRotation.js +++ b/src/utils/serverRotation.js @@ -90,7 +90,8 @@ export async function tryWithRotation(operation, options = {}) { /** * Get the effective server URL for the current provider - * For classworkscloud, returns the primary server (last known working) or first server in the list + * For classworkscloud, returns the primary server (last successful in current session) or first server in the list + * Note: Primary server tracking resets on page reload * For other providers, returns the configured domain * @returns {string} Server URL */ @@ -115,34 +116,8 @@ export function isRotationEnabled() { } /** - * Check if an error is a network error that should trigger server rotation - * @param {Error} error - The error to check - * @returns {boolean} - */ -function isNetworkError(error) { - // Network errors from axios typically have no response or specific error codes - if (!error.response) { - return true; // No response = network issue - } - - // Server timeout or connection errors - if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT' || - error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') { - return true; - } - - // 5xx errors might indicate server issues worth retrying - const status = error.response?.status; - if (status >= 500) { - return true; - } - - return false; -} - -/** - * Try operation with primary server first, fallback to rotation on network errors only - * This is more efficient than always trying rotation for every request + * Try operation with primary server first, fallback to rotation on any error + * Uses the last successful server first for efficiency * @param {Function} operation - Async function that takes a serverUrl and returns a promise * @param {Object} options - Options * @param {string} options.provider - Provider type (optional, defaults to current setting) @@ -163,14 +138,9 @@ export async function tryWithPrimaryServer(operation, options = {}) { try { return await operation(primaryUrl); } catch (error) { - // Only rotate to other servers if it's a network error - if (isNetworkError(error)) { - console.warn(`Primary server ${primaryUrl} failed with network error, trying rotation...`); - // Use full rotation, which will update the primary server if a different one succeeds - return await tryWithRotation(operation, options); - } - - // For non-network errors (e.g., 404, 401, validation errors), don't retry with other servers - throw error; + // On any error, try rotation with all servers + console.warn(`Primary server ${primaryUrl} failed, trying rotation...`); + // Use full rotation, which will update the primary server if a different one succeeds + return await tryWithRotation(operation, options); } } From 17f7b46d1438c0a77e393afa6859ebf46781d703 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:25:37 +0000 Subject: [PATCH 5/5] Remove primary server optimization, use unified rotation logic for all requests Co-authored-by: Sunwuyuan <88357633+Sunwuyuan@users.noreply.github.com> --- src/utils/api.js | 6 ++-- src/utils/providers/kvServerProvider.js | 22 ++++++------- src/utils/serverRotation.js | 44 ++----------------------- 3 files changed, 16 insertions(+), 56 deletions(-) diff --git a/src/utils/api.js b/src/utils/api.js index 370e0b5..d4847a2 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -1,6 +1,6 @@ import axios from "@/axios/axios"; import {getSetting} from "@/utils/settings"; -import {tryWithPrimaryServer, isRotationEnabled} from "@/utils/serverRotation"; +import {tryWithRotation, isRotationEnabled} from "@/utils/serverRotation"; // Helper function to check if provider is valid for API calls const isValidProvider = () => { @@ -35,9 +35,9 @@ export const getNamespaceInfo = async () => { } try { - // Use primary server with fallback for classworkscloud provider + // Use rotation for classworkscloud provider if (isRotationEnabled()) { - const response = await tryWithPrimaryServer(async (serverUrl) => { + const response = await tryWithRotation(async (serverUrl) => { return await axios.get(`${serverUrl}/kv/_info`, { headers: getHeaders(), }); diff --git a/src/utils/providers/kvServerProvider.js b/src/utils/providers/kvServerProvider.js index 1b5f9d9..93952d5 100644 --- a/src/utils/providers/kvServerProvider.js +++ b/src/utils/providers/kvServerProvider.js @@ -1,7 +1,7 @@ import axios from "@/axios/axios"; import {formatResponse, formatError} from "../dataProvider"; import {getSetting} from "../settings"; -import {tryWithPrimaryServer, isRotationEnabled} from "../serverRotation"; +import {tryWithRotation, isRotationEnabled} from "../serverRotation"; // Helper function to get request headers with kvtoken const getHeaders = () => { @@ -23,9 +23,9 @@ const getHeaders = () => { export const kvServerProvider = { async loadNamespaceInfo() { try { - // Use primary server with fallback for classworkscloud provider + // Use rotation for classworkscloud provider if (isRotationEnabled()) { - return await tryWithPrimaryServer(async (serverUrl) => { + return await tryWithRotation(async (serverUrl) => { const res = await axios.get(`${serverUrl}/kv/_info`, { headers: getHeaders(), }); @@ -52,9 +52,9 @@ export const kvServerProvider = { async updateNamespaceInfo(data) { try { - // Use primary server with fallback for classworkscloud provider + // Use rotation for classworkscloud provider if (isRotationEnabled()) { - return await tryWithPrimaryServer(async (serverUrl) => { + return await tryWithRotation(async (serverUrl) => { const res = await axios.put(`${serverUrl}/kv/_info`, data, { headers: getHeaders(), }); @@ -78,9 +78,9 @@ export const kvServerProvider = { async loadData(key) { try { - // Use primary server with fallback for classworkscloud provider + // Use rotation for classworkscloud provider if (isRotationEnabled()) { - return await tryWithPrimaryServer(async (serverUrl) => { + return await tryWithRotation(async (serverUrl) => { const res = await axios.get(`${serverUrl}/kv/${key}`, { headers: getHeaders(), }); @@ -108,9 +108,9 @@ export const kvServerProvider = { async saveData(key, data) { try { - // Use primary server with fallback for classworkscloud provider + // Use rotation for classworkscloud provider if (isRotationEnabled()) { - return await tryWithPrimaryServer(async (serverUrl) => { + return await tryWithRotation(async (serverUrl) => { await axios.post(`${serverUrl}/kv/${key}`, data, { headers: getHeaders(), }); @@ -171,9 +171,9 @@ export const kvServerProvider = { skip: skip.toString() }); - // Use primary server with fallback for classworkscloud provider + // Use rotation for classworkscloud provider if (isRotationEnabled()) { - return await tryWithPrimaryServer(async (serverUrl) => { + return await tryWithRotation(async (serverUrl) => { const res = await axios.get(`${serverUrl}/kv/_keys?${params}`, { headers: getHeaders(), }); diff --git a/src/utils/serverRotation.js b/src/utils/serverRotation.js index cc1aa15..ee0c286 100644 --- a/src/utils/serverRotation.js +++ b/src/utils/serverRotation.js @@ -11,9 +11,6 @@ const CLASSWORKS_CLOUD_SERVERS = [ "https://kv-service.wuyuan.dev", ]; -// Track the current primary server (the one that's currently working) -let primaryServerUrl = null; - /** * Get the list of servers to try for the given provider * @param {string} provider - The provider type @@ -62,11 +59,6 @@ export async function tryWithRotation(operation, options = {}) { onServerTried({ url: serverUrl, status: "success", tried: [...triedServers] }); } - // Update primary server on success (for classworkscloud provider) - if (provider === "classworkscloud") { - primaryServerUrl = serverUrl; - } - return result; } catch (error) { lastError = error; @@ -90,8 +82,7 @@ export async function tryWithRotation(operation, options = {}) { /** * Get the effective server URL for the current provider - * For classworkscloud, returns the primary server (last successful in current session) or first server in the list - * Note: Primary server tracking resets on page reload + * For classworkscloud, returns the first server in the list * For other providers, returns the configured domain * @returns {string} Server URL */ @@ -99,8 +90,7 @@ export function getEffectiveServerUrl() { const provider = getSetting("server.provider"); if (provider === "classworkscloud") { - // Return primary server if available, otherwise first in list - return primaryServerUrl || CLASSWORKS_CLOUD_SERVERS[0]; + return CLASSWORKS_CLOUD_SERVERS[0]; } return getSetting("server.domain") || ""; @@ -114,33 +104,3 @@ export function isRotationEnabled() { const provider = getSetting("server.provider"); return provider === "classworkscloud"; } - -/** - * Try operation with primary server first, fallback to rotation on any error - * Uses the last successful server first for efficiency - * @param {Function} operation - Async function that takes a serverUrl and returns a promise - * @param {Object} options - Options - * @param {string} options.provider - Provider type (optional, defaults to current setting) - * @returns {Promise} Result from the operation - */ -export async function tryWithPrimaryServer(operation, options = {}) { - const provider = options.provider || getSetting("server.provider"); - - // For non-classworkscloud providers, just use the configured domain - if (provider !== "classworkscloud") { - const serverUrl = getSetting("server.domain"); - return await operation(serverUrl); - } - - // For classworkscloud, try primary server first - const primaryUrl = getEffectiveServerUrl(); - - try { - return await operation(primaryUrl); - } catch (error) { - // On any error, try rotation with all servers - console.warn(`Primary server ${primaryUrl} failed, trying rotation...`); - // Use full rotation, which will update the primary server if a different one succeeds - return await tryWithRotation(operation, options); - } -}