/// <summary> /// Gets or creates a distributed object. /// </summary> /// <typeparam name="T">The type of the distributed object.</typeparam> /// <typeparam name="TImpl">The type of the implementation.</typeparam> /// <param name="serviceName">The unique name of the service.</param> /// <param name="name">The unique name of the object.</param> /// <param name="remote">Whether to create the object remotely too.</param> /// <param name="factory">The object factory.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>The distributed object.</returns> public async Task <T> GetOrCreateAsync <T, TImpl>( string serviceName, string name, bool remote, Func <string, DistributedObjectFactory, Cluster, SerializationService, ILoggerFactory, TImpl> factory, CancellationToken cancellationToken = default) where TImpl : DistributedObjectBase, T { if (_disposed == 1) { throw new ObjectDisposedException("DistributedObjectFactory"); } var info = new DistributedObjectInfo(serviceName, name); async ValueTask <DistributedObjectBase> CreateAsync(DistributedObjectInfo info2, CancellationToken token) { var x = factory(name, this, _cluster, _serializationService, _loggerFactory); x.ObjectDisposed = OnObjectDisposed; // this is why is has to be DistributedObjectBase // initialize the object if (remote) { var requestMessage = ClientCreateProxyCodec.EncodeRequest(x.Name, x.ServiceName); _ = await _cluster.Messaging.SendAsync(requestMessage, token).CfAwait(); } x.OnInitialized(); _logger.LogDebug("Initialized ({Object}) distributed object.", info2); return(x); } // try to get the object - thanks to the concurrent dictionary there will be only 1 task // and if several concurrent requests are made, they will all await that same task var o = await _objects.GetOrAddAsync(info, CreateAsync, cancellationToken).CfAwait(); // race condition: maybe the factory has been disposed and is already disposing // objects and will ignore this new object even though it has been added to the // dictionary, so take care of it ourselves if (_disposed == 1) { await o.DisposeAsync().CfAwait(); throw new ObjectDisposedException("DistributedObjectFactory"); } // if the object is a T then we can return it if (o is T t) { return(t); } // otherwise, the client was already used to retrieve an object with the specified service // name and object name, but a different type, for instance IHList<int> vs IHList<string>, // and we just cannot support this = throw throw new HazelcastException($"A distributed object with the specified service name ({serviceName}) " + $"and object name ({name}) exists but of type {o.GetType().ToCsString()}, " + $"instead of {typeof(T).ToCsString()}."); }
public async ValueTask <NearCache> GetOrCreateNearCacheAsync(string name, NearCacheOptions options, CancellationToken cancellationToken = default) { return(await _caches.GetOrAddAsync(name, async (n, token) => { var nearCache = new NearCache(n, _cluster, _serializationService, _loggerFactory, options, GetMaxToleratedMissCount()); await InitializeNearCache(nearCache).CfAwait(); return nearCache; }, cancellationToken).CfAwait()); }
/// <summary> /// Gets or creates a distributed object. /// </summary> /// <typeparam name="T">The type of the distributed object.</typeparam> /// <typeparam name="TImpl">The type of the implementation.</typeparam> /// <param name="serviceName">The unique name of the service.</param> /// <param name="name">The unique name of the object.</param> /// <param name="remote">Whether to create the object remotely too.</param> /// <param name="factory">The object factory.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>The distributed object.</returns> public async Task <T> GetOrCreateAsync <T, TImpl>( string serviceName, string name, bool remote, Func <string, DistributedObjectFactory, Cluster, ISerializationService, ILoggerFactory, TImpl> factory, CancellationToken cancellationToken = default) where TImpl : DistributedObjectBase, T { if (_disposed == 1) { throw new ObjectDisposedException("DistributedObjectFactory"); } await _cluster.ThrowIfNotConnected().CAF(); var k = new DistributedObjectInfo(serviceName, name); async ValueTask <DistributedObjectBase> CreateAsync(DistributedObjectInfo info, CancellationToken token) { var x = factory(name, this, _cluster, _serializationService, _loggerFactory); x.OnDispose = ObjectDisposed; // this is why is has to be DistributedObjectBase // initialize the object if (remote) { var requestMessage = ClientCreateProxyCodec.EncodeRequest(x.Name, x.ServiceName); _ = await _cluster.Messaging.SendAsync(requestMessage, token).CAF(); } x.OnInitialized(); _logger.LogDebug("Initialized '{ServiceName}/{Name}' distributed object.", info.ServiceName, info.Name); return(x); } // try to get the object - thanks to the concurrent dictionary there will be only 1 task // and if several concurrent requests are made, they will all await that same task var o = await _objects.GetOrAddAsync(k, CreateAsync, cancellationToken).CAF(); // race condition: maybe the factory has been disposed and is already disposing // objects and will ignore this new object even though it has been added to the // dictionary, so take care of it ourselves if (_disposed == 1) { await o.DisposeAsync().CAF(); throw new ObjectDisposedException("DistributedObjectFactory"); } if (o is T t) { return(t); } // if the object that was retrieved is not of the right type, it's a problem // preserve the existing object, but throw throw new InvalidCastException("A distributed object with the specified service name and name, but " + "with a different type, has already been created."); }
public async Task Test() { var d = new ConcurrentAsyncDictionary <string, ValueItem <int> >(); var i = 0; ValueTask <ValueItem <int> > Factory(string key, CancellationToken _) { i++; return(new ValueTask <ValueItem <int> >(new ValueItem <int>(2))); } var v = await d.GetOrAddAsync("a", Factory); Assert.AreEqual(2, v.Value); Assert.AreEqual(1, i); v = await d.GetOrAddAsync("a", Factory); Assert.AreEqual(2, v.Value); Assert.AreEqual(1, i); var entries = new List <string>(); await foreach (var(key, value) in d) { entries.Add($"{key}:{value}"); } Assert.AreEqual(1, entries.Count); Assert.AreEqual("a:2", entries[0]); var(hasValue, gotValue) = await d.TryGetAsync("a"); Assert.IsTrue(hasValue); Assert.AreEqual(2, gotValue.Value); Assert.IsTrue(d.TryRemove("a")); Assert.IsFalse(d.TryRemove("a")); (hasValue, _) = await d.TryGetAsync("a"); Assert.IsFalse(hasValue);
/// <summary> /// Tries to get a value from, or add a value to, the cache. /// </summary> /// <param name="keyData">The key data.</param> /// <param name="valueFactory">A factory that accepts the key data and returns the value data.</param> /// <returns>An attempt at getting or adding a value to the cache.</returns> public async Task <Attempt <object> > TryGetOrAddAsync(IData keyData, Func <IData, Task <IData> > valueFactory) { // if it's in the cache already, return it // (and TryGetAsync counts a hit) // (otherwise, TryGetAsync counts a miss, so we don't have to do it here) var(hasEntry, valueObject) = await TryGetAsync(keyData).CfAwait(); if (hasEntry) { return(valueObject); } // kick eviction policy if needed if (_evictionPolicy != EvictionPolicy.None && _entries.Count >= _maxSize) { await EvictEntries().CfAwait(); } // if the cache is full, directly return the un-cached value if (_evictionPolicy == EvictionPolicy.None && _entries.Count >= _maxSize && !await _entries.ContainsKeyAsync(keyData).CfAwait()) { return(Attempt.Fail(ToCachedValue(await valueFactory(keyData).CfAwait()))); } async ValueTask <NearCacheEntry> CreateEntry(IData _, CancellationToken __) { var valueData = await valueFactory(keyData).CfAwait(); var cachedValue = ToCachedValue(valueData); return(CreateCacheEntry(keyData, cachedValue)); // null if cachedValue is null } var entry = await _entries.GetOrAddAsync(keyData, CreateEntry).CfAwait(); if (entry != null) // null if ValueObject would have been null { Statistics.NotifyEntryAdded(); return(entry.ValueObject); } // the entry will not stick in _entries // and we haven't notified statistics return(Attempt.Failed); }