Sto utilizzando StackExchange.Redis
(SE.R d'ora in poi) nella mia applicazione Nancy. C'è un singolo globale ConnectionMultiplexer
che viene automaticamente trasmesso da Nancy's TinyIoC
tramite parametri del costruttore, e ogni volta che provo e uso GetDatabase
e uno dei metodi *Async
(i metodi di sincronizzazione iniziano solo a fallire dopo che uno dei metodi asincroni sono stati tentati) la mia applicazione deadlock.StackExchange.Redis Deadlocking
Guardando il mio pile parallele sembra che io ho quattro thread:
- Il thread che ha chiamato
Result
su uno dei miei compiti che utilizza SE.R. (C'è molta roba di Nancy in pila, poi una chiamata alla mia biblioteca che utilizza SE.R e una chiamata aResult
. La parte superiore della pila èMonitor.Wait
). - Un thread che ha generato altri due thread. Presumo che questo sia gestito da SE.R. Inizia con
Native to Managed Transition
,ThreadHelper.ThreadStart
e nella parte superiore della pila èThreadHelper.ThreadStart_Context
. - Una piccola pila che è bloccato in questo modo:
Monitor.Wait
Monitor.Wait
SocketManager.WriteAllQueues
SocketManager.cctor.AnonymousMethod__16
- Un'altra piccola pila che assomiglia a questo:
Managed to Native Transition
SocketManager.ReadImpl
SocketManager.Read
SocketManager.cctor.AnonymousMethod__19
Sono quasi sicuro che questo è una situazione di stallo di qualche tipo. Penso anche che potrebbe avere qualcosa a che fare con this question. Ma non ho idea di cosa fare al riguardo.
Il ConnectionMultiplexer
è istituito in un Nancy IRegistrations
con il seguente codice:
var configOpts = new ConfigurationOptions {
EndPoints = {
RedisHost,
},
Password = RedisPass,
AllowAdmin = false,
ClientName = ApplicationName,
ConnectTimeout = 10000,
SyncTimeout = 5000,
};
var mux = ConnectionMultiplexer.Connect(configOpts);
yield return new InstanceRegistration(typeof (ConnectionMultiplexer), mux);
mux
è l'istanza che viene condivisa da tutto il codice che ne facciano richiesta nella loro lista parametro del costruttore.
Ho una classe chiamata SchemaCache
. Un piccolo pezzo di esso (che include il codice che genera l'errore in questione) segue:
public SchemaCache(ConnectionMultiplexer connectionMultiplexer) {
ConnectionMultiplexer = connectionMultiplexer;
}
private ConnectionMultiplexer ConnectionMultiplexer { get; set; }
private async Task<string[]> Cached(string key, bool forceFetch, Func<string[]> fetch) {
var db = ConnectionMultiplexer.GetDatabase();
return forceFetch || !await db.KeyExistsAsync(key)
? await CacheSetSet(db, key, await Task.Run(fetch))
: await CacheGetSet(db, key);
}
private static async Task<string[]> CacheSetSet(IDatabaseAsync db, string key, string[] values) {
await db.KeyDeleteAsync(key);
await db.SetAddAsync(key, EmptyCacheSentinel);
var keysSaved = values
.Append(EmptyCacheSentinel)
.Select(val => db.SetAddAsync(key, val))
.ToArray()
.Append(db.KeyExpireAsync(key, TimeSpan.FromDays(1)));
await Task.WhenAll(keysSaved);
return values;
}
private static async Task<string[]> CacheGetSet(IDatabaseAsync db, string key) {
var results = await db.SetMembersAsync(key);
return results.Select(rv => (string) rv).Without(EmptyCacheSentinel).ToArray();
}
// There are a bunch of these public methods:
public async Task<IEnumerable<string>> UseCache1(bool forceFetch = false) {
return await Cached("the_key_i_want", forceFetch,() => {
using (var cnn = MakeConnectionToDatabase("server", "databaseName")) {
// Uses Dapper:
return cnn.Query<string>("--expensive sql query").ToArray();
}
});
}
Ho anche una classe che fa uso di questo in un metodo che richiede alcune delle informazioni dalla cache:
public OtherClass(SchemaCache cache) {
Cache = cache;
}
private SchemaCache Cache { get; set; }
public Result GetResult(Parameter parameter) {
return Cache.UseCache1().Result
.Where(r => Cache.UseCache2(r).Result.Contains(parameter))
.Select(r => CheckResult(r))
.FirstOrDefault(x => x != null);
}
Tutto di quanto sopra funziona bene in LinqPad dove c'è solo una istanza di tutto in questione. Invece fallisce con uno TimeoutException
(e successivamente un'eccezione su nessuna connessione disponibile).L'unica differenza è che ottengo un'istanza della cache tramite dependency injection, e sono abbastanza sicuro che Nancy usi Tasks per parallelizzare le richieste.
Mostraci il tuo codice. –
Ancora cercando di riassumere. – Crisfole
Stai per caso utilizzando una transazione o un lotto? C'è un modo molto semplice per bloccarti lì (che è altrettanto facilmente risolto) –