Example #1
0
        public async Task TryWriteAsync(string stateName, Func <Stream, Task> write)
        {
            var(filepath, stream) = OpenWriteLocal(stateName);

            try
            {
                // Will throw if it does not want the stream to be saved.
                await write(stream).ConfigureAwait(false);
            }
            catch
            {
                // In case of failure, delete the broken file.
                stream.Dispose();
                try { File.Delete(filepath); }
                catch { /* Ignored silently. */ }
                throw;
            }

            // Copy the stream to the inner cache provider.
            if (_inner != null)
            {
                await _inner.TryWriteAsync(stateName, async remote =>
                {
                    using (remote)
                    {
                        using (var local = File.OpenRead(filepath))
                        {
                            await local.CopyToAsync(remote).ConfigureAwait(false);
                        }
                    }
                }).ConfigureAwait(false);
            }

            // Delete local files over the limit.
            var inDirectory = Directory.EnumerateFiles(Path.Combine(_directory, stateName))
                              .OrderByDescending(path => path)
                              .ToArray();

            for (var i = MaxCacheFilesCount; i < inDirectory.Length; ++i)
            {
                try { File.Delete(Path.Combine(_directory, stateName, inDirectory[i])); }
                catch { /* Ignored silently. Some of them might still be memory-mapped ! */ }
            }
        }
        /// <summary> Attempt to save this projection to the destination stream. </summary>
        /// <remarks>
        ///     The returned task does not access the projection in any way, so the
        ///     projection may be safely accessed before the task has finished executing.
        /// </remarks>
        /// <returns>
        ///     True if saving was successful, false if it failed.
        /// </returns>
        public async Task <bool> TrySaveAsync(CancellationToken cancel = default)
        {
            if (_possiblyInconsistent)
            {
                _log?.Warning($"[{Name}] state is possibly inconsistent, not saving.");
                return(false);
            }

            if (_cacheProvider == null)
            {
                _log?.Warning($"[{Name}] no write cache provider !");
                return(false);
            }

            var sequence = Sequence;
            var current  = Current;

            _log?.Debug($"[{Name}] saving to seq {sequence}.");

            var sw = Stopwatch.StartNew();

            var wrote = 0L;

            try
            {
                await _cacheProvider.TryWriteAsync(Name, async destination =>
                {
                    try
                    {
                        using (destination)
                        {
                            using (var wr = new BinaryWriter(destination, Encoding.UTF8, true))
                                wr.Write(sequence);

                            if (!await _projection.TrySaveAsync(destination, current, cancel))
                            {
                                _log?.Warning($"[{Name}] projection failed to save.");
                                throw new Exception("INTERNAL.DO.NOT.SAVE");
                            }

                            if (!destination.CanWrite)
                            {
                                throw new Exception("Projection saving closed the stream ! ");
                            }

                            using (var wr = new BinaryWriter(destination, Encoding.UTF8, true))
                                wr.Write(sequence);

                            wrote = destination.Position;
                        }
                    }
                    catch (Exception e) when(e.Message == "INTERNAL.DO.NOT.SAVE")
                    {
                    }
                    catch (Exception e)
                    {
                        _log?.Warning($"[{Name}] while saving to cache.", e);
                        throw new Exception("INTERNAL.DO.NOT.SAVE", e);
                    }
                }).ConfigureAwait(false);

                if (wrote == 0)
                {
                    _log?.Warning($"[{Name}] caching is disabled for this projection.");
                }
                else
                {
                    _log?.Info($"[{Name}] saved {wrote} bytes to cache in {sw.Elapsed:mm':'ss'.'fff}.");
                }

                return(true);
            }
            catch (Exception e) when(e.Message == "INTERNAL.DO.NOT.SAVE")
            {
                // The inner function asked us not to save, and already logged the reason.
                // But if an inner exception is included, throw it (preserving the existing
                // stack trace).
                if (e.InnerException != null)
                {
                    ExceptionDispatchInfo.Capture(e.InnerException).Throw();
                }

                return(false);
            }
            catch (Exception ex)
            {
                _log?.Warning($"[{Name}] when opening write cache.", ex);
                return(false);
            }
        }