Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,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 @@ -35,9 +33,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
Original file line number Diff line number Diff line change
Expand Up @@ -16,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.9.17" />
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
233 changes: 218 additions & 15 deletions dotnet/src/dotnetframework/Providers/Cache/GxRedis/GxRedis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
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.Encryption;
using GeneXus.Services;
Expand All @@ -22,6 +23,10 @@ public sealed class Redis : ICacheService2
IDatabase _redisDatabase;
#if NETCORE
bool _multitenant;
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;
Expand All @@ -32,12 +37,11 @@ public Redis(string connectionString)
{
_redisConnectionOptions = ConfigurationOptions.Parse(connectionString);
_redisConnectionOptions.AllowAdmin = true;
InitLocalCache();
}

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 @@ -76,8 +80,24 @@ public Redis()
_redisConnectionOptions = ConfigurationOptions.Parse(address);
}
_redisConnectionOptions.AllowAdmin = true;
InitLocalCache();
}
}
private void InitLocalCache()
{
#if NETCORE
if (EnvVarReader.GetEnvironmentValue(GXServices.CACHE_SERVICE, GXServices.REDIS_CACHE_SERVICE, "HYBRID", out string envVarValue) && envVarValue.Equals(true.ToString(), 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 Down Expand Up @@ -120,50 +140,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 @@ -175,6 +218,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 @@ -212,19 +330,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 @@ -293,5 +412,89 @@ 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
if (_localCache == null)
return;
_localCache.Remove(key);
#endif
}
void ClearAllCachesLocal()
{
#if NETCORE
if (_localCache == null)
return;
_localCache.Compact(1.0);
#endif
}

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

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
if (_localCache != null)
_localCache.Set(cacheid, prefix, LocalCacheTTL(LOCAL_CACHE_PERSISTENT_KEY_TTL));
#endif
}
private void SetLocal<T>(string key, T value, int duration)
{
#if NETCORE
if (_localCache != null)
_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