@@ -87,30 +87,25 @@ public interface IBotDataStore<T>
8787 }
8888
8989 /// <summary>
90- /// Volatile in-memory implementation of <see cref="IBotDataStore{BotData}"/>
90+ /// Volitile in-memory implementation of <see cref="IBotDataStore{BotData}"/>
9191 /// </summary>
92+ /// <remarks>
93+ /// NOTE: This uses an internal dictionary with no culling so it should not be used for production code at all, as it will eventually just use all of your memory.
94+ /// </remarks>
9295 public class InMemoryDataStore : IBotDataStore < BotData >
9396 {
94- // This should be moved to autofac registration
95- internal static readonly MemoryCache store = new MemoryCache ( nameof ( InMemoryDataStore ) , new NameValueCollection ( ) { { "PhysicalMemoryLimitPercentage" , "50" } } ) ;
96-
97- private static CacheItemPolicy cacheItemPolicy = new CacheItemPolicy ( ) { SlidingExpiration = TimeSpan . FromMinutes ( 15 ) } ;
98-
97+ internal readonly ConcurrentDictionary < string , string > store = new ConcurrentDictionary < string , string > ( ) ;
9998 private readonly Dictionary < BotStoreType , object > locks = new Dictionary < BotStoreType , object > ( )
10099 {
101100 { BotStoreType . BotConversationData , new object ( ) } ,
102101 { BotStoreType . BotPrivateConversationData , new object ( ) } ,
103102 { BotStoreType . BotUserData , new object ( ) }
104103 } ;
105104
106- public InMemoryDataStore ( )
107- {
108- }
109-
110105 async Task < BotData > IBotDataStore < BotData > . LoadAsync ( IAddress key , BotStoreType botStoreType , CancellationToken cancellationToken )
111106 {
112- string serializedData = ( string ) store . Get ( GetKey ( key , botStoreType ) ) ;
113- if ( serializedData != null )
107+ string serializedData ;
108+ if ( store . TryGetValue ( GetKey ( key , botStoreType ) , out serializedData ) )
114109 return Deserialize ( serializedData ) ;
115110 return new BotData ( eTag : String . Empty ) ;
116111 }
@@ -119,24 +114,27 @@ async Task IBotDataStore<BotData>.SaveAsync(IAddress key, BotStoreType botStoreT
119114 {
120115 lock ( locks [ botStoreType ] )
121116 {
122- string storeKey = GetKey ( key , botStoreType ) ;
123- string oldValue = ( string ) store . Get ( storeKey ) ;
124117 if ( botData . Data != null )
125118 {
126- if ( oldValue != null )
119+ store . AddOrUpdate ( GetKey ( key , botStoreType ) , ( dictionaryKey ) =>
127120 {
128- ValidateETag ( botData , oldValue ) ;
129- }
130- botData . ETag = Guid . NewGuid ( ) . ToString ( "n" ) ;
131- store . Set ( GetKey ( key , botStoreType ) , Serialize ( botData ) , cacheItemPolicy ) ;
121+ botData . ETag = Guid . NewGuid ( ) . ToString ( "n" ) ;
122+ return Serialize ( botData ) ;
123+ } , ( dictionaryKey , value ) =>
124+ {
125+ ValidateETag ( botData , value ) ;
126+ botData . ETag = Guid . NewGuid ( ) . ToString ( "n" ) ;
127+ return Serialize ( botData ) ;
128+ } ) ;
132129 }
133130 else
134131 {
135132 // remove record on null
136- if ( oldValue != null )
133+ string value ;
134+ if ( store . TryGetValue ( GetKey ( key , botStoreType ) , out value ) )
137135 {
138- ValidateETag ( botData , oldValue ) ;
139- store . Remove ( storeKey ) ;
136+ ValidateETag ( botData , value ) ;
137+ store . TryRemove ( GetKey ( key , botStoreType ) , out value ) ;
140138 return ;
141139 }
142140 }
@@ -258,14 +256,14 @@ public enum CachingBotDataStoreConsistencyPolicy
258256 }
259257
260258 /// <summary>
261- /// Caches data for <see cref="BotDataBase{T}"/> and wraps the data in <see cref="BotData"/> to be stored in <see cref="CachingBotDataStore.inner"/>
259+ /// Caches changes until FlushAsync() is called
260+ /// NOTE: Despite the name, this is NOT a cache of access of the inner store, but is a change cache of changes that will be pushed to
261+ /// inner store.
262262 /// </summary>
263263 public class CachingBotDataStore : IBotDataStore < BotData >
264264 {
265265 private readonly IBotDataStore < BotData > inner ;
266- // this should be moved to autofac registration
267- internal static readonly MemoryCache cache = new MemoryCache ( nameof ( CachingBotDataStore ) , new NameValueCollection ( ) { { "PhysicalMemoryLimitPercentage" , "50" } } ) ;
268- internal static CacheItemPolicy cacheItemPolicy = new CacheItemPolicy ( ) { SlidingExpiration = TimeSpan . FromMinutes ( 15 ) } ;
266+ internal readonly Dictionary < IAddress , CacheEntry > cache = new Dictionary < IAddress , CacheEntry > ( ) ;
269267 private readonly CachingBotDataStoreConsistencyPolicy dataConsistencyPolicy ;
270268
271269 public CachingBotDataStore ( IBotDataStore < BotData > inner , CachingBotDataStoreConsistencyPolicy dataConsistencyPolicy )
@@ -274,31 +272,23 @@ public CachingBotDataStore(IBotDataStore<BotData> inner, CachingBotDataStoreCons
274272 this . dataConsistencyPolicy = dataConsistencyPolicy ;
275273 }
276274
277- public long GetCount ( ) { return cache . GetCount ( ) ; }
278-
279275 internal class CacheEntry
280276 {
281277 public BotData BotConversationData { set ; get ; }
282278 public BotData BotPrivateConversationData { set ; get ; }
283279 public BotData BotUserData { set ; get ; }
284280 }
285281
286- private string GetCacheKey ( IAddress key )
287- {
288- return $ "{ key . BotId } .{ key . ChannelId } .{ key . ConversationId } .{ key . UserId } ";
289- }
290-
291282 async Task < bool > IBotDataStore < BotData > . FlushAsync ( IAddress key , CancellationToken cancellationToken )
292283 {
293- var cacheKey = GetCacheKey ( key ) ;
294- CacheEntry entry = ( CacheEntry ) cache . Get ( GetCacheKey ( key ) ) ;
295- if ( entry != null )
284+ CacheEntry entry ;
285+ if ( cache . TryGetValue ( key , out entry ) )
296286 {
297287 // Removing the cached entry to make sure that we are not leaking
298288 // flushed entries when CachingBotDataStore is registered as a singleton object.
299289 // Also since this store is not updating ETags on LoadAsync(...), there
300290 // will be a conflict if we reuse the cached entries after flush.
301- cache . Remove ( cacheKey ) ;
291+ cache . Remove ( key ) ;
302292 await this . Save ( key , entry , cancellationToken ) ;
303293 return true ;
304294 }
@@ -310,13 +300,12 @@ async Task<bool> IBotDataStore<BotData>.FlushAsync(IAddress key, CancellationTok
310300
311301 async Task < BotData > IBotDataStore < BotData > . LoadAsync ( IAddress key , BotStoreType botStoreType , CancellationToken cancellationToken )
312302 {
313- var cacheKey = GetCacheKey ( key ) ;
314- CacheEntry cacheEntry = ( CacheEntry ) cache . Get ( GetCacheKey ( key ) ) ;
303+ CacheEntry cacheEntry ;
315304 BotData value = null ;
316- if ( cacheEntry == null )
305+ if ( ! cache . TryGetValue ( key , out cacheEntry ) )
317306 {
318307 cacheEntry = new CacheEntry ( ) ;
319- cache . Add ( cacheKey , cacheEntry , cacheItemPolicy ) ;
308+ cache . Add ( key , cacheEntry ) ;
320309 value = await LoadFromInnerAndCache ( cacheEntry , botStoreType , key , cancellationToken ) ;
321310 }
322311 else
@@ -356,15 +345,14 @@ async Task<BotData> IBotDataStore<BotData>.LoadAsync(IAddress key, BotStoreType
356345
357346 async Task IBotDataStore < BotData > . SaveAsync ( IAddress key , BotStoreType botStoreType , BotData value , CancellationToken cancellationToken )
358347 {
359- var cacheKey = GetCacheKey ( key ) ;
360- CacheEntry cacheEntry = ( CacheEntry ) cache . Get ( GetCacheKey ( key ) ) ;
361- if ( cacheEntry == null )
348+ CacheEntry entry ;
349+ if ( ! cache . TryGetValue ( key , out entry ) )
362350 {
363- cacheEntry = new CacheEntry ( ) ;
364- cache . Add ( cacheKey , cacheEntry , cacheItemPolicy ) ;
351+ entry = new CacheEntry ( ) ;
352+ cache . Add ( key , entry ) ;
365353 }
366354
367- SetCachedValue ( cacheEntry , botStoreType , value ) ;
355+ SetCachedValue ( entry , botStoreType , value ) ;
368356 }
369357
370358 private async Task < BotData > LoadFromInnerAndCache ( CacheEntry cacheEntry , BotStoreType botStoreType , IAddress key , CancellationToken token )
0 commit comments