Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dotnet/src/dotnetcore/GxClasses/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
[assembly: InternalsVisibleTo("DotNetCoreChunkedTest")]
[assembly: InternalsVisibleTo("DotNetCoreChunkedTest")]
[assembly: InternalsVisibleTo("GeneXus.OpenTelemetry.Diagnostics")]
[assembly: InternalsVisibleTo("GxRedis")]
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ public class GXSessionServiceFactory
private static readonly IGXLogger log = GXLoggerFactory.GetLogger<GXSessionServiceFactory>();

static ISessionService sessionService;
static string REDIS = "REDIS";
static string DATABASE = "DATABASE";
public static ISessionService GetProvider()
{
if (sessionService != null)
Expand All @@ -29,9 +27,9 @@ public static ISessionService GetProvider()
//Compatibility
if (string.IsNullOrEmpty(className))
{
if (providerService.Name.Equals(REDIS, StringComparison.OrdinalIgnoreCase))
if (providerService.Name.Equals(GXServices.REDIS_CACHE_SERVICE, StringComparison.OrdinalIgnoreCase))
type = typeof(GxRedisSession);
else if (providerService.Name.Equals(DATABASE, StringComparison.OrdinalIgnoreCase))
else if (providerService.Name.Equals(GXServices.DATABASE_CACHE_SERVICE, StringComparison.OrdinalIgnoreCase))
type = typeof(GxDatabaseSession);
}
else
Expand Down
5 changes: 3 additions & 2 deletions dotnet/src/dotnetcore/Providers/Cache/GxRedis/GxRedis.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PropertyGroup>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<PackageTags>Redis</PackageTags>
<PackageId>GeneXus.Redis.Core</PackageId>
<DefineConstants>NETCORE</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<AppDesignerFolder>Properties</AppDesignerFolder>
Expand All @@ -15,7 +16,7 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="..\..\..\..\dotnetframework\Providers\Cache\GxRedis\GxRedis.cs" Link="GxRedis.cs" />
<Compile Include="..\..\..\..\dotnetframework\Providers\Cache\GxRedis\GxRedis.cs" Link="GxRedis.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="StackExchange.Redis" Version="2.6.122" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public class GXServices
public static string STORAGE_SERVICE = "Storage";
public static string STORAGE_APISERVICE = "StorageAPI";
public static string CACHE_SERVICE = "Cache";
public static string REDIS_CACHE_SERVICE = "Redis";
public static string DATABASE_CACHE_SERVICE = "DATABASE";
public static string DATA_ACCESS_SERVICE = "DataAccess";
public static string SESSION_SERVICE = "Session";
public static string WEBNOTIFICATIONS_SERVICE = "WebNotifications";
Expand Down Expand Up @@ -47,6 +49,7 @@ public static GXServices Instance
}
set { }
}

public void AddService(string name, GXService service)
{
services[name] = service;
Expand Down
229 changes: 214 additions & 15 deletions dotnet/src/dotnetframework/Providers/Cache/GxRedis/GxRedis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
#if NETCORE
using GeneXus.Application;
using GxClasses.Helpers;
using Microsoft.Extensions.Caching.Memory;
#endif
using GeneXus.Services;
using GeneXus.Utils;
using StackExchange.Redis;
Expand All @@ -15,19 +19,24 @@ public sealed class Redis : ICacheService2

ConnectionMultiplexer _redisConnection;
IDatabase _redisDatabase;
#if NETCORE
MemoryCache _localCache;
private const double DEFAULT_LOCAL_CACHE_FACTOR = 0.8;
private static readonly TimeSpan LOCAL_CACHE_PERSISTENT_KEY_TTL = TimeSpan.FromMinutes(5);

#endif
ConfigurationOptions _redisConnectionOptions;
private const int REDIS_DEFAULT_PORT = 6379;
public int redisSessionTimeout;

public Redis(string connectionString)
{
_redisConnectionOptions = ConfigurationOptions.Parse(connectionString);
_redisConnectionOptions.AllowAdmin = true;
}

public Redis(string connectionString, int sessionTimeout)
public Redis(string connectionString, int sessionTimeout):this(connectionString)
{
_redisConnectionOptions = ConfigurationOptions.Parse(connectionString);
_redisConnectionOptions.AllowAdmin = true;
redisSessionTimeout = sessionTimeout;
}
public Redis()
Expand Down Expand Up @@ -56,8 +65,25 @@ public Redis()
_redisConnectionOptions = ConfigurationOptions.Parse(address);
}
_redisConnectionOptions.AllowAdmin = true;
InitLocalCache(providerService);
}
}
private void InitLocalCache(GXService providerService)
{
#if NETCORE
string localCache = providerService.Properties.Get("ENABLE_MEMORY_CACHE");
if (!string.IsNullOrEmpty(localCache) && localCache.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase))
{
GXLogging.Debug(log, "Using Redis Hybrid mode with local memory cache.");
_localCache = new MemoryCache(new MemoryCacheOptions());
}
else
{
GXLogging.Debug(log, "Using Redis only mode without local memory cache.");
}
#endif
}

IDatabase RedisDatabase
{
get
Expand All @@ -78,50 +104,73 @@ public void Clear(string cacheid, string key)
public void ClearKey(string key)
{
RedisDatabase.KeyDelete(key);
ClearKeyLocal(key);
}

public void ClearCache(string cacheid)
{
Nullable<long> prefix = new Nullable<long>(KeyPrefix(cacheid).Value + 1);
RedisDatabase.StringSet(cacheid, prefix);
SetPersistentLocal(cacheid, prefix);
}

public void ClearAllCaches()
{
var endpoints = _redisConnection.GetEndPoints(true);
IConnectionMultiplexer multiplexer = RedisDatabase.Multiplexer;
System.Net.EndPoint[] endpoints = multiplexer.GetEndPoints(true);
foreach (var endpoint in endpoints)
{
var server = _redisConnection.GetServer(endpoint);
var server = multiplexer.GetServer(endpoint);
server.FlushAllDatabases();
}
ClearAllCachesLocal();
}

public bool KeyExpire(string cacheid, string key, TimeSpan expiry, CommandFlags flags = CommandFlags.None)
{
Task<bool> t = RedisDatabase.KeyExpireAsync(Key(cacheid, key), expiry, flags);
t.Wait();
return t.Result;
string fullKey = Key(cacheid, key);
bool expirationSaved = RedisDatabase.KeyExpire(fullKey, expiry, flags);
if (expirationSaved)
KeyExpireLocal(fullKey);
return expirationSaved;
}

public bool KeyExists(string cacheid, string key)
{
Task<bool> t = RedisDatabase.KeyExistsAsync(Key(cacheid, key));
t.Wait();
return t.Result;
string fullKey = Key(cacheid, key);

if (KeyExistsLocal(fullKey))
{
GXLogging.Debug(log, $"KeyExists hit local cache {fullKey}");
return true;
}

return RedisDatabase.KeyExists(fullKey);
}

private bool Get<T>(string key, out T value)
{
if (GetLocal(key, out value))
{
GXLogging.Debug(log, $"Get<T> hit local cache {key}");
return true;
}

if (default(T) == null)
{
value = Deserialize<T>(RedisDatabase.StringGet(key));
if (value == null) GXLogging.Debug(log, "Get<T>, misses key '" + key + "'");
if (value == null)
GXLogging.Debug(log, "Get<T>, misses key '" + key + "'");
else
SetLocal(key, value);
return value != null;
}
else
{
if (RedisDatabase.KeyExists(key))
{
value = Deserialize<T>(RedisDatabase.StringGet(key));
SetLocal(key, value);
return true;
}
else
Expand All @@ -133,6 +182,81 @@ private bool Get<T>(string key, out T value)
}
}

#if NETCORE
public IDictionary<string, T> GetAll<T>(string cacheid, IEnumerable<string> keys)
{
if (keys == null) return null;

var results = new Dictionary<string, T>();
var keysToFetch = new List<string>();

foreach (string k in keys)
{
string fullKey = Key(cacheid, k);
if (GetLocal<T>(fullKey, out T value))
{
GXLogging.Debug(log, $"Get<T> hit local cache {fullKey}");
results[k] = value;
}
else
{
keysToFetch.Add(k);
}
}

if (keysToFetch.Count > 0)
{
var prefixedKeys = Key(cacheid, keysToFetch);
RedisValue[] values = RedisDatabase.StringGet(prefixedKeys.ToArray());

int i = 0;
foreach (string k in keysToFetch)
{
string fullKey = Key(cacheid, k);
T value = Deserialize<T>(values[i]);
results[k] = value;

SetLocal(fullKey, value);
i++;
}
}

return results;
}
public void SetAll<T>(string cacheid, IEnumerable<string> keys, IEnumerable<T> values, int duration = 0)
{
if (keys == null || values == null || keys.Count() != values.Count())
return;

IEnumerable<RedisKey> prefixedKeys = Key(cacheid, keys);
IEnumerator<T> valuesEnumerator = values.GetEnumerator();
KeyValuePair<RedisKey, RedisValue>[] redisBatch = new KeyValuePair<RedisKey, RedisValue>[prefixedKeys.Count()];

int i = 0;
foreach (RedisKey redisKey in prefixedKeys)
{
if (valuesEnumerator.MoveNext())
{
T value = valuesEnumerator.Current;
redisBatch[i] = new KeyValuePair<RedisKey, RedisValue>(redisKey, Serialize(value));
SetLocal<T>(redisKey.ToString(), value, duration);
}
i++;
}
if (redisBatch.Length > 0)
{
if (duration > 0)
{
foreach (var pair in redisBatch)
RedisDatabase.StringSet(pair.Key, pair.Value, TimeSpan.FromMinutes(duration));
}
else
{
RedisDatabase.StringSet(redisBatch);
}
}
}
#else
public IDictionary<string, T> GetAll<T>(string cacheid, IEnumerable<string> keys)
{
if (keys != null)
Expand Down Expand Up @@ -170,19 +294,20 @@ public void SetAll<T>(string cacheid, IEnumerable<string> keys, IEnumerable<T> v
RedisDatabase.StringSet(dictionary);
}
}

#endif
private void Set<T>(string key, T value, int duration)
{
GXLogging.Debug(log, "Set<T> key:" + key + " value " + value + " valuetype:" + value.GetType());
if (duration > 0)
RedisDatabase.StringSet(key, Serialize(value), TimeSpan.FromMinutes(duration));
else
RedisDatabase.StringSet(key, Serialize(value));
SetLocal(key, value, duration);
}

private void Set<T>(string key, T value)
{
RedisDatabase.StringSet(key, Serialize(value));
Set<T>(key, value, 0);
}

public bool Get<T>(string cacheid, string key, out T value)
Expand Down Expand Up @@ -245,5 +370,79 @@ static T Deserialize<T>(string value)
opts.Converters.Add(new ObjectToInferredTypesConverter());
return JsonSerializer.Deserialize<T>(value, opts);
}
#if NETCORE
private TimeSpan LocalCacheTTL(int durationMinutes)
{
return LocalCacheTTL(durationMinutes > 0 ? TimeSpan.FromMinutes(durationMinutes) : (TimeSpan?)null);
}
private TimeSpan LocalCacheTTL(TimeSpan? ttl)
{
return ttl.HasValue ? TimeSpan.FromTicks((long)(ttl.Value.Ticks * DEFAULT_LOCAL_CACHE_FACTOR)) : LOCAL_CACHE_PERSISTENT_KEY_TTL;
}
#endif
private void ClearKeyLocal(string key)
{
#if NETCORE
_localCache?.Remove(key);
#endif
}
void ClearAllCachesLocal()
{
#if NETCORE
_localCache?.Compact(1.0);
#endif
}

private void KeyExpireLocal(string fullKey)
{
#if NETCORE
_localCache?.Remove(fullKey);
#endif
}
private bool KeyExistsLocal(string fullKey)
{
#if NETCORE
return _localCache?.TryGetValue(fullKey, out _) ?? false;
#else
return false;
#endif
}

private void SetLocal<T>(string key, T value)
{
#if NETCORE
if (_localCache != null)
{
TimeSpan? redisTTL = RedisDatabase.KeyTimeToLive(key);
_localCache.Set(key, value, LocalCacheTTL(redisTTL));
}
#endif
}
private void SetPersistentLocal(string cacheid, long? prefix)
{
#if NETCORE
_localCache?.Set(cacheid, prefix, LocalCacheTTL(LOCAL_CACHE_PERSISTENT_KEY_TTL));
#endif
}
private void SetLocal<T>(string key, T value, int duration)
{
#if NETCORE
_localCache?.Set(key, value, LocalCacheTTL(duration));
#endif
}
private bool GetLocal<T>(string key, out T value)
{
#if NETCORE
if (_localCache == null)
{
value = default(T);
return false;
}
return _localCache.TryGetValue(key, out value);
#else
value = default(T);
return false;
#endif
}
}
}
Loading