/// <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; } }