/// <summary>
        /// Ctor.
        /// </summary>
        /// <param name="storagePath">Base path to store persisted data. If null data will not be written to disk.</param>
        /// <param name="shutdownEvent">Event to trigger when shutdown is desired. If null no event is triggered.</param>
        /// <param name="preferredPrivateMemorySizeMB">Preferred working set size for the current process in MB. Above this size data will be released and garbage will be collected.</param>
        /// <param name="maximumPrivateMemorySizeMB">Maximum working set size for the current process in MB. Above this size the shutdownEvent will be triggered.</param>
        /// <param name="maintenanceParallelismLimit">Maximum parallel threads for periodic data maintenance (e.g. compaction).</param>
        public DataManager(string storagePath, ManualResetEventSlim shutdownEvent, ulong preferredPrivateMemorySizeMB,
                           ulong maximumPrivateMemorySizeMB, int maintenanceParallelismLimit)
        {
            // just in case our hosting process has not force-compiled the schemas, we do so here as a catch-all
            Schemas.Precompile();

            if (!string.IsNullOrEmpty(storagePath))
            {
                if (!Path.IsPathRooted(storagePath))
                {
                    throw new ArgumentException("Path must be rooted", "storagePath");
                }

                if (!Directory.Exists(storagePath))
                {
                    Directory.CreateDirectory(storagePath);
                }
            }

            if (maintenanceParallelismLimit <= 0)
            {
                maintenanceParallelismLimit = Environment.ProcessorCount;
            }

            this.shutdownEvent = shutdownEvent;
            this.StoragePath   = storagePath;

            if (maximumPrivateMemorySizeMB > 0)
            {
                if (shutdownEvent == null)
                {
                    throw new ArgumentNullException("shutdownEvent",
                                                    "Cannot set max working set size without shutdown event.");
                }

                if (maximumPrivateMemorySizeMB < preferredPrivateMemorySizeMB)
                {
                    throw new ArgumentException("Maximum working set size is smaller than preferred.",
                                                "maximumPrivateMemorySizeMB");
                }

                if (preferredPrivateMemorySizeMB == 0)
                {
                    preferredPrivateMemorySizeMB = maximumPrivateMemorySizeMB;
                }

                this.maximumPrivateMemorySizeMB   = maximumPrivateMemorySizeMB;
                this.preferredPrivateMemorySizeMB = preferredPrivateMemorySizeMB;
            }
            else if (preferredPrivateMemorySizeMB > 0)
            {
                throw new ArgumentException("Cannot set preferred working set size without max", "preferredPrivateMemorySizeMB");
            }

            this.CompactionConfiguration = new DataCompactionConfiguration();
            this.SealTime       = DefaultSealTime;
            this.MaximumDataAge = DefaultMaximumDataAge;
            this.taskRunner     = new SemaphoreTaskRunner(maintenanceParallelismLimit);

            // This is a really ridiculous hack to get the full machine name of the current system. Sigh.
            this.LocalSourceName = Dns.GetHostEntry("localhost").HostName;
            //this.CompactionConfiguration = new DataCompactionConfig();

            this.maintenanceTimer = new Timer(_ => this.PeriodicMaintenance(), null, TimeSpan.Zero,
                                              this.MaintenanceInterval);
            // TODO: configurable, for now:
            // 128KB blocks, 1MB large buffer multiples, 128MB max stream
            this.MemoryStreamManager = new RecyclableMemoryStreamManager(1 << 17, 1 << 20, 1 << 27);
            this.MemoryStreamManager.AggressiveBufferReturn = true;
            // If we got a memory limit put a cap on free data
            if (this.preferredPrivateMemorySizeMB > 4)
            {
                var memoryPerPool = (long)this.preferredPrivateMemorySizeMB / 2;
                this.MemoryStreamManager.MaximumFreeLargePoolBytes     =
                    this.MemoryStreamManager.MaximumFreeSmallPoolBytes = memoryPerPool / 2;
            }
        }