From 55c529f4323f3cd5e2dc629238c76b32ae0d79db Mon Sep 17 00:00:00 2001 From: n0099 Date: Wed, 12 Jun 2024 20:42:33 +0000 Subject: [PATCH] * move invocations to`IPostSaver.Save()` & `UserSaver.Save()` inside try block to ensure their `OnPostSave()` get invoked in finally block even if exception was thrown to fix 388ddc9026ef45491d7db448a4e50f6a27024d3d @ `CrawlFacade.SaveCrawled()` * move the action returned by `Save()` into `IDisposable.Dispose()` to allow locks also get released by GC * lift variable `(newly|already)LockedImages` from method `Save()` to nullable class field @ ReplyContentImageSaver.cs @ c#/crawler --- .../src/Tieba/Crawl/Facade/CrawlFacade.cs | 17 +++---- .../Crawl/Saver/ReplyContentImageSaver.cs | 48 ++++++++++--------- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/c#/crawler/src/Tieba/Crawl/Facade/CrawlFacade.cs b/c#/crawler/src/Tieba/Crawl/Facade/CrawlFacade.cs index 13644ce7..2f30ed11 100644 --- a/c#/crawler/src/Tieba/Crawl/Facade/CrawlFacade.cs +++ b/c#/crawler/src/Tieba/Crawl/Facade/CrawlFacade.cs @@ -47,20 +47,21 @@ public virtual void Dispose() using var transaction = db.Database.BeginTransaction(IsolationLevel.ReadCommitted); var postSaver = postSaverFactory(Posts); - var savedPosts = Posts.IsEmpty ? null : postSaver.Save(db); - var userSaver = userSaverFactory(_users); - userSaver.Save(db, - postSaver.CurrentPostType, - postSaver.UserFieldUpdateIgnorance, - postSaver.UserFieldRevisionIgnorance); - - OnBeforeCommitSave(db, userSaver); try { + var savedPosts = Posts.IsEmpty ? null : postSaver.Save(db); + userSaver.Save(db, + postSaver.CurrentPostType, + postSaver.UserFieldUpdateIgnorance, + postSaver.UserFieldRevisionIgnorance); + + OnBeforeCommitSave(db, userSaver); + db.TimestampingEntities(); _ = db.SaveChanges(); transaction.Commit(); + if (savedPosts != null) OnPostCommitSave(savedPosts, stoppingToken); return savedPosts; } diff --git a/c#/crawler/src/Tieba/Crawl/Saver/ReplyContentImageSaver.cs b/c#/crawler/src/Tieba/Crawl/Saver/ReplyContentImageSaver.cs index 5c66bd3d..9f06fb3c 100644 --- a/c#/crawler/src/Tieba/Crawl/Saver/ReplyContentImageSaver.cs +++ b/c#/crawler/src/Tieba/Crawl/Saver/ReplyContentImageSaver.cs @@ -1,9 +1,26 @@ namespace tbm.Crawler.Tieba.Crawl.Saver; -public class ReplyContentImageSaver(ILogger logger) +public sealed class ReplyContentImageSaver(ILogger logger) : IDisposable { private static readonly ConcurrentDictionary GlobalLockedImagesInReplyKeyByUrlFilename = new(); + private Dictionary? _newlyLockedImages; + private Dictionary? _alreadyLockedImages; + + public void Dispose() + { + try + { + if (_newlyLockedImages != null && _newlyLockedImages.Any(pair => + !GlobalLockedImagesInReplyKeyByUrlFilename.TryRemove(pair))) + throw new InvalidOperationException(); + } + finally + { + _newlyLockedImages?.Values().ForEach(Monitor.Exit); + _alreadyLockedImages?.Values().ForEach(Monitor.Exit); + } + } public Action Save(CrawlerDbContext db, IEnumerable replies) { @@ -36,28 +53,28 @@ where images.Keys.Contains(e.UrlFilename) var newImages = images .ExceptByKey(existingImages.Keys).ToDictionary(); - var newlyLockedImages = newImages + _newlyLockedImages = newImages .Where(pair => GlobalLockedImagesInReplyKeyByUrlFilename.TryAdd(pair.Key, pair.Value)) .ToDictionary(); - newlyLockedImages.Values() + _newlyLockedImages.Values() .Where(image => !Monitor.TryEnter(image, TimeSpan.FromSeconds(10))) .ForEach(image => logger.LogWarning( "Wait for locking newly locked image {} timed out after 10s", image.UrlFilename)); - var alreadyLockedImages = GlobalLockedImagesInReplyKeyByUrlFilename + _alreadyLockedImages = GlobalLockedImagesInReplyKeyByUrlFilename .IntersectByKey(newImages - .Keys().Except(newlyLockedImages.Keys())) + .Keys().Except(_newlyLockedImages.Keys())) .ToDictionary(); - alreadyLockedImages.Values() + _alreadyLockedImages.Values() .Where(image => !Monitor.TryEnter(image, TimeSpan.FromSeconds(10))) .ForEach(image => logger.LogWarning( "Wait for locking already locked image {} timed out after 10s", image.UrlFilename)); - if (alreadyLockedImages.Count != 0) + if (_alreadyLockedImages.Count != 0) existingImages = existingImages .Concat(( from e in db.ImageInReplies.AsTracking() - where alreadyLockedImages.Keys().Contains(e.UrlFilename) + where _alreadyLockedImages.Keys().Contains(e.UrlFilename) select e).ToDictionary(e => e.UrlFilename)) .ToDictionary(); (from existing in existingImages.Values @@ -88,19 +105,6 @@ on existing.UrlFilename equals replyContentImage.ImageInReply.UrlFilename .ExceptBy(existingReplyContentImages.Select(e => (e.Pid, e.UrlFilename)), e => (e.Pid, e.ImageInReply.UrlFilename))); - return () => - { - try - { - if (newlyLockedImages.Any(pair => - !GlobalLockedImagesInReplyKeyByUrlFilename.TryRemove(pair))) - throw new InvalidOperationException(); - } - finally - { - newlyLockedImages.Values().ForEach(Monitor.Exit); - alreadyLockedImages.Values().ForEach(Monitor.Exit); - } - }; + return Dispose; } }