private void AssertWriteLockContended(AsyncUpgradeableReaderWriterLock rwLock) { using (CancellationTokenSource timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(200))) { Assert.Throws <TaskCanceledException>(() => rwLock.EnterWriteLockAsync(timeout.Token).ConfigureAwait(false).GetAwaiter().GetResult()); } }
private Task HoldWriteLockAsync(AsyncUpgradeableReaderWriterLock rwLock, ManualResetEventSlim setWhenLockHeld, CancellationToken cancellationToken) { return(Task.Run(() => { using (rwLock.EnterWriteLockAsync(CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult()) { setWhenLockHeld.Set(); cancellationToken.WaitHandle.WaitOne(); } })); }
private async Task ReloadTrainingDataLowMemoryAsync(bool finalize, CancellationToken cancellationToken) { using (var trainingDataWriteLock = await m_trainingDataLockAsync.EnterWriteLockAsync(cancellationToken).ConfigureAwait(false)) using (var recSourcesWriteLock = await m_recSourcesLockAsync.EnterWriteLockAsync(cancellationToken).ConfigureAwait(false)) { Logging.Log.Info("Reloading training data and prerequisites and retraining rec sources. Rec sources will not be available until retraining all rec sources is complete."); Stopwatch totalTimer = Stopwatch.StartNew(); m_recSources.Clear(); m_trainingData = null; m_usernames = null; m_animes = null; m_prereqs = null; m_finalized = false; GC.Collect(); Logging.Log.Info("Rec sources cleared."); Logging.Log.InfoFormat("Memory use: {0} bytes", GC.GetTotalMemory(forceFullCollection: false)); // Load new training data // If this throws an error, m_trainingData is left null. Methods that use m_trainingData should check it for null. using (IMalTrainingDataLoader malTrainingDataLoader = m_trainingDataLoaderFactory.GetTrainingDataLoader()) using (CancellationTokenSource faultCanceler = new CancellationTokenSource()) using (CancellationTokenSource faultOrUserCancel = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, faultCanceler.Token)) { Stopwatch trainingDataTimer = Stopwatch.StartNew(); CancellableTask <MalTrainingData> trainingDataTask = new CancellableTask <MalTrainingData>( malTrainingDataLoader.LoadMalTrainingDataAsync(faultOrUserCancel.Token), faultCanceler); Task trainingDataTimerTask = trainingDataTask.Task.ContinueWith(task => { trainingDataTimer.Stop(); Logging.Log.InfoFormat("Training data loaded. {0} users, {1} animes, {2} entries. Took {3}.", task.Result.Users.Count, task.Result.Animes.Count, task.Result.Users.Keys.Sum(userId => task.Result.Users[userId].Entries.Count), trainingDataTimer.Elapsed); }, cancellationToken, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.NotOnCanceled | TaskContinuationOptions.NotOnFaulted, TaskScheduler.Current); Stopwatch prereqsTimer = Stopwatch.StartNew(); CancellableTask <IDictionary <int, IList <int> > > prereqsTask = new CancellableTask <IDictionary <int, IList <int> > >( malTrainingDataLoader.LoadPrerequisitesAsync(faultOrUserCancel.Token), faultCanceler); Task prereqsTimerTask = prereqsTask.Task.ContinueWith(task => { prereqsTimer.Stop(); int numPrereqs = task.Result.Values.Sum(prereqList => prereqList.Count); Logging.Log.InfoFormat("Prerequisites loaded. {0} prerequisites for {1} animes. Took {2}.", numPrereqs, task.Result.Count, prereqsTimer.Elapsed); }, cancellationToken, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.NotOnCanceled | TaskContinuationOptions.NotOnFaulted, TaskScheduler.Current); await AsyncUtils.WhenAllCancelOnFirstExceptionDontWaitForCancellations(trainingDataTask, prereqsTask).ConfigureAwait(false); m_trainingData = trainingDataTask.Task.Result; m_usernames = GetUsernamesFromTrainingData(m_trainingData); m_animes = m_trainingData.Animes; m_prereqs = prereqsTask.Task.Result; await trainingDataTimerTask.ConfigureAwait(false); await prereqsTimerTask.ConfigureAwait(false); } GC.Collect(); Logging.Log.InfoFormat("Memory use: {0} bytes", GC.GetTotalMemory(forceFullCollection: false)); // Then retrain all loaded rec sources. if (m_recSourceFactories.Count == 0) { Logging.Log.Info("No rec sources to retrain."); } else { Logging.Log.Info("Retraining rec sources."); object recSourcesLockAndMemFence = new object(); List <Task> recSourceTrainTasksList = new List <Task>(); // ToList() so we can unload a rec source as we iterate if it errors while training. foreach (string recSourceNameLoopVar in m_recSourceFactories.Keys.ToList()) { string recSourceName = recSourceNameLoopVar; // avoid capturing the loop var ITrainableJsonRecSource recSource = m_recSourceFactories[recSourceName](); Task recSourceTrainTask = Task.Run(() => { Logging.Log.InfoFormat("Retraining rec source {0} ({1}).", recSourceName, recSource); Stopwatch trainTimer = Stopwatch.StartNew(); try { recSource.Train(m_trainingData, m_usernames, cancellationToken); trainTimer.Stop(); Logging.Log.InfoFormat("Trained rec source {0} ({1}). Took {2}.", recSourceName, recSource, trainTimer.Elapsed); lock (recSourcesLockAndMemFence) { m_recSources[recSourceName] = recSource; } } catch (OperationCanceledException) { Logging.Log.InfoFormat("Canceled while retraining rec source {0} ({1}). Unloading it.", recSourceName, recSource); lock (recSourcesLockAndMemFence) { m_recSourceFactories.Remove(recSourceName); } throw; } catch (Exception ex) { Logging.Log.ErrorFormat("Error retraining rec source {0} ({1}): {2} Unloading it.", ex, recSourceName, recSource, ex.Message); lock (recSourcesLockAndMemFence) { m_recSourceFactories.Remove(recSourceName); } } }, cancellationToken); recSourceTrainTasksList.Add(recSourceTrainTask); } // Wait for all to complete or cancellation. There should not be any exceptions other than OperationCanceledException. await Task.WhenAll(recSourceTrainTasksList).ConfigureAwait(false); lock (recSourcesLockAndMemFence) { ; // just for the fence } } if (finalize) { m_trainingData = null; m_usernames = null; m_finalized = true; Logging.Log.Info("Finalized rec sources."); } totalTimer.Stop(); Logging.Log.InfoFormat("All rec sources retrained with the latest data. Total time: {0}", totalTimer.Elapsed); } GC.Collect(); Logging.Log.InfoFormat("Memory use: {0} bytes", GC.GetTotalMemory(forceFullCollection: false)); }
public async Task LoadRecSourceAsync(Func <ITrainableJsonRecSource> recSourceFactory, string name, bool replaceExisting, CancellationToken cancellationToken) { // Acquire read lock on current list, write lock on pending list // If name already exists on current list and replaceExisting = false, throw. // If name already exists on pending list, throw. // Otherwise, add name to pending list, release locks, and proceed. using (var recSourcesReadLock = await m_recSourcesLockAsync.EnterReadLockAsync(cancellationToken).ConfigureAwait(false)) using (var pendingRecSourcesWriteLock = await m_pendingRecSourcesLockAsync.EnterWriteLockAsync(cancellationToken).ConfigureAwait(false)) { if (m_recSources.ContainsKey(name) && !replaceExisting) { throw new RecServiceErrorException(new Error(errorCode: ErrorCodes.Unknown, message: string.Format("A recommendation source with the name \"{0}\" already exists.", name))); } if (m_pendingRecSources.Contains(name)) { throw new RecServiceErrorException(new Error(errorCode: ErrorCodes.Unknown, message: string.Format("A recommendation source with the name \"{0}\" is currently being trained.", name))); } m_pendingRecSources.Add(name); } try { // Need to hold read lock on training data while training so that a retrain can't happen while we're training here. // Rec sources must be trained with the current m_trainingData, not an old version. using (var trainingDataReadLock = await m_trainingDataLockAsync.EnterReadLockAsync(cancellationToken).ConfigureAwait(false)) { if (m_trainingData == null && !m_finalized) { throw new RecServiceErrorException(new Error(errorCode: ErrorCodes.NoTrainingData, message: "A reload/retrain in low memory mode failed, leaving the rec service without training data or rec sources. Issue a ReloadTrainingData command to load training data, then load rec sources.")); } else if (m_trainingData == null && m_finalized) { throw new RecServiceErrorException(new Error(errorCode: ErrorCodes.Finalized, message: "Rec sources have been finalized. A non-finalized retrain must be invoked to be able to add rec sources.")); } ITrainableJsonRecSource recSource = recSourceFactory(); Logging.Log.InfoFormat("Training rec source with name \"{0}\", replaceExisting={1}: {2}", name, replaceExisting, recSource); Stopwatch timer = Stopwatch.StartNew(); recSource.Train(m_trainingData, m_usernames, cancellationToken); timer.Stop(); Logging.Log.InfoFormat("Trained rec source {0}. Took {1}", name, timer.Elapsed); using (var recSourcesWriteLock = await m_recSourcesLockAsync.EnterWriteLockAsync(cancellationToken).ConfigureAwait(false)) using (var pendingRecSourcesWriteLock = await m_pendingRecSourcesLockAsync.EnterWriteLockAsync(cancellationToken).ConfigureAwait(false)) { m_recSources[name] = recSource; m_recSourceFactories[name] = recSourceFactory; m_pendingRecSources.Remove(name); } } } catch (Exception) { m_pendingRecSources.Remove(name); throw; } Logging.Log.InfoFormat("Loaded rec source {0}.", name); GC.Collect(); Logging.Log.InfoFormat("Memory use: {0} bytes", GC.GetTotalMemory(forceFullCollection: false)); }