diff --git a/src/Plugins/BotSharp.Plugin.Membase/BotSharp.Plugin.Membase.csproj b/src/Plugins/BotSharp.Plugin.Membase/BotSharp.Plugin.Membase.csproj
index 85a11d4f2..864ce6db8 100644
--- a/src/Plugins/BotSharp.Plugin.Membase/BotSharp.Plugin.Membase.csproj
+++ b/src/Plugins/BotSharp.Plugin.Membase/BotSharp.Plugin.Membase.csproj
@@ -10,6 +10,7 @@
+
diff --git a/src/Plugins/BotSharp.Plugin.Membase/GraphDb/MembaseGraphDb.cs b/src/Plugins/BotSharp.Plugin.Membase/GraphDb/MembaseGraphDb.cs
index 6848f7c70..be606fcbb 100644
--- a/src/Plugins/BotSharp.Plugin.Membase/GraphDb/MembaseGraphDb.cs
+++ b/src/Plugins/BotSharp.Plugin.Membase/GraphDb/MembaseGraphDb.cs
@@ -1,12 +1,6 @@
-using BotSharp.Abstraction.Graph;
-using BotSharp.Abstraction.Graph.Models;
-using BotSharp.Abstraction.Graph.Options;
-using BotSharp.Abstraction.Models;
-using BotSharp.Abstraction.Options;
-using BotSharp.Abstraction.Utilities;
-using BotSharp.Plugin.Membase.Interfaces;
-using Microsoft.Extensions.Logging;
-using System.Text.Json;
+using Polly;
+using Polly.Timeout;
+using Refit;
namespace BotSharp.Plugin.Membase.GraphDb;
@@ -28,6 +22,33 @@ public MembaseGraphDb(
public string Provider => "membase";
+ private const int RetryCount = 3;
+
+ private AsyncPolicy BuildRetryPolicy()
+ {
+ var settings = _services.GetRequiredService();
+ var timeoutSeconds = (double)settings.TimeoutSecond / RetryCount;
+
+ var timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(timeoutSeconds));
+
+ var retryPolicy = Policy
+ .Handle()
+ .Or()
+ .Or()
+ .Or(ex => ex.StatusCode == HttpStatusCode.ServiceUnavailable)
+ .WaitAndRetryAsync(
+ retryCount: RetryCount,
+ sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
+ onRetry: (ex, timespan, retryAttempt, _) =>
+ {
+ _logger.LogWarning(ex,
+ "CypherQueryAsync retry {RetryAttempt}/{MaxRetries} after {Delay}s. Exception: {Message}",
+ retryAttempt, RetryCount, timespan.TotalSeconds, ex.Message);
+ });
+
+ return Policy.WrapAsync(retryPolicy, timeoutPolicy);
+ }
+
public async Task ExecuteQueryAsync(string query, GraphQueryExecuteOptions? options = null)
{
if (string.IsNullOrEmpty(options?.GraphId))
@@ -36,14 +57,17 @@ public async Task ExecuteQueryAsync(string query, GraphQueryEx
}
var args = options?.Arguments ?? new();
+ var argLogs = JsonSerializer.Serialize(args, BotSharpOptions.defaultJsonOptions);
try
{
- var response = await _membaseApi.CypherQueryAsync(options!.GraphId, new CypherQueryRequest
- {
- Query = query,
- Parameters = args
- });
+ var retryPolicy = BuildRetryPolicy();
+ var response = await retryPolicy.ExecuteAsync(() =>
+ _membaseApi.CypherQueryAsync(options!.GraphId, new CypherQueryRequest
+ {
+ Query = query,
+ Parameters = args
+ }));
return new GraphQueryResult
{
@@ -52,10 +76,14 @@ public async Task ExecuteQueryAsync(string query, GraphQueryEx
Result = JsonSerializer.Serialize(response.Data)
};
}
+ catch (ApiException ex)
+ {
+ _logger.LogError($"Error when executing query in {Provider} graph db:\r\n{ex.Content}\r\n{query}\r\n{argLogs}");
+ throw;
+ }
catch (Exception ex)
{
- var argLogs = args.Select(x => (new KeyValue(x.Key, x.Value.ConvertToString(BotSharpOptions.defaultJsonOptions))).ToString());
- _logger.LogError(ex, $"Error when executing query in {Provider} graph db. (Query: {query}), (Argments: \r\n{string.Join("\r\n", argLogs)})");
+ _logger.LogError(ex, $"Error when executing query in {Provider} graph db. (Query: {query}), (Argments: \r\n{argLogs})");
throw;
}
}
diff --git a/src/Plugins/BotSharp.Plugin.Membase/MembasePlugin.cs b/src/Plugins/BotSharp.Plugin.Membase/MembasePlugin.cs
index c2fa22254..07531db30 100644
--- a/src/Plugins/BotSharp.Plugin.Membase/MembasePlugin.cs
+++ b/src/Plugins/BotSharp.Plugin.Membase/MembasePlugin.cs
@@ -33,7 +33,8 @@ public void RegisterDI(IServiceCollection services, IConfiguration config)
.ConfigureHttpClient(c =>
{
c.BaseAddress = new Uri(settings.Host);
- c.Timeout = TimeSpan.FromSeconds(settings.TimeoutSecond);
+ // Timeout is set by MembaseGrapbDb internally, but we set it here as well to ensure that the Refit client does not timeout before the graph db does.
+ c.Timeout = TimeSpan.FromSeconds(90);
});
services.AddScoped();
diff --git a/src/Plugins/BotSharp.Plugin.Membase/Settings/MembaseSettings.cs b/src/Plugins/BotSharp.Plugin.Membase/Settings/MembaseSettings.cs
index cb1b344ee..7960a51b2 100644
--- a/src/Plugins/BotSharp.Plugin.Membase/Settings/MembaseSettings.cs
+++ b/src/Plugins/BotSharp.Plugin.Membase/Settings/MembaseSettings.cs
@@ -7,6 +7,6 @@ public class MembaseSettings
public string Host { get; set; } = "localhost";
public string ProjectId { get; set; } = string.Empty;
public string ApiKey { get; set; } = string.Empty;
- public int TimeoutSecond { get; set; } = 10;
+ public int TimeoutSecond { get; set; } = 30;
public GraphInstance[] GraphInstances { get; set; } = [];
}
diff --git a/src/Plugins/BotSharp.Plugin.Membase/Using.cs b/src/Plugins/BotSharp.Plugin.Membase/Using.cs
index 755fe4fb4..e31467b5d 100644
--- a/src/Plugins/BotSharp.Plugin.Membase/Using.cs
+++ b/src/Plugins/BotSharp.Plugin.Membase/Using.cs
@@ -2,11 +2,15 @@
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading.Tasks;
+global using System.Net;
+global using System.Net.Http;
+global using System.Text.Json;
global using Microsoft.AspNetCore.Authorization;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
+global using Microsoft.Extensions.Logging;
global using BotSharp.Abstraction.Users;
global using BotSharp.Abstraction.Knowledges;
@@ -15,3 +19,8 @@
global using BotSharp.Plugin.Membase.Models;
global using BotSharp.Plugin.Membase.Services;
global using BotSharp.Plugin.Membase.Settings;
+global using BotSharp.Abstraction.Graph;
+global using BotSharp.Abstraction.Graph.Models;
+global using BotSharp.Abstraction.Graph.Options;
+global using BotSharp.Abstraction.Options;
+global using BotSharp.Plugin.Membase.Interfaces;