{"id":46354877,"url":"https://github.com/soenneker/soenneker.sets.concurrent.slidingwindow","last_synced_at":"2026-06-06T08:00:30.673Z","repository":{"id":341887159,"uuid":"1171728873","full_name":"soenneker/soenneker.sets.concurrent.slidingwindow","owner":"soenneker","description":"A high-throughput, thread-safe set whose bucketed entries automatically expire after a fixed time window.","archived":false,"fork":false,"pushed_at":"2026-06-05T21:06:01.000Z","size":146,"stargazers_count":0,"open_issues_count":5,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-05T22:05:19.027Z","etag":null,"topics":["auto-expire","concurrency","concurrent","csharp","de-dupe","dedupe","dotnet","object","set","sets","slidingwindow","slidingwindowconcurrentset","threadsafe"],"latest_commit_sha":null,"homepage":"https://soenneker.com","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/soenneker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"soenneker","thanks_dev":"soenneker"}},"created_at":"2026-03-03T14:47:07.000Z","updated_at":"2026-06-05T20:01:36.000Z","dependencies_parsed_at":"2026-06-05T22:03:10.622Z","dependency_job_id":null,"html_url":"https://github.com/soenneker/soenneker.sets.concurrent.slidingwindow","commit_stats":null,"previous_names":["soenneker/soenneker.sets.concurrent.slidingwindow"],"tags_count":36,"template":false,"template_full_name":null,"purl":"pkg:github/soenneker/soenneker.sets.concurrent.slidingwindow","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soenneker%2Fsoenneker.sets.concurrent.slidingwindow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soenneker%2Fsoenneker.sets.concurrent.slidingwindow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soenneker%2Fsoenneker.sets.concurrent.slidingwindow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soenneker%2Fsoenneker.sets.concurrent.slidingwindow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/soenneker","download_url":"https://codeload.github.com/soenneker/soenneker.sets.concurrent.slidingwindow/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soenneker%2Fsoenneker.sets.concurrent.slidingwindow/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33973868,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-06T02:00:07.033Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["auto-expire","concurrency","concurrent","csharp","de-dupe","dedupe","dotnet","object","set","sets","slidingwindow","slidingwindowconcurrentset","threadsafe"],"created_at":"2026-03-05T00:07:00.439Z","updated_at":"2026-06-06T08:00:30.656Z","avatar_url":"https://github.com/soenneker.png","language":"C#","funding_links":["https://github.com/sponsors/soenneker","https://thanks.dev/soenneker"],"categories":[],"sub_categories":[],"readme":"[![](https://img.shields.io/nuget/v/soenneker.sets.concurrent.slidingwindow.svg?style=for-the-badge)](https://www.nuget.org/packages/soenneker.sets.concurrent.slidingwindow/)\n[![](https://img.shields.io/github/actions/workflow/status/soenneker/soenneker.sets.concurrent.slidingwindow/publish-package.yml?style=for-the-badge)](https://github.com/soenneker/soenneker.sets.concurrent.slidingwindow/actions/workflows/publish-package.yml)\n[![](https://img.shields.io/nuget/dt/soenneker.sets.concurrent.slidingwindow.svg?style=for-the-badge)](https://www.nuget.org/packages/soenneker.sets.concurrent.slidingwindow/)\n[![](https://img.shields.io/github/actions/workflow/status/soenneker/soenneker.sets.concurrent.slidingwindow/codeql.yml?label=CodeQL\u0026style=for-the-badge)](https://github.com/soenneker/soenneker.sets.concurrent.slidingwindow/actions/workflows/codeql.yml)\n\n# ![](https://user-images.githubusercontent.com/4441470/224455560-91ed3ee7-f510-4041-a8d2-3fc093025112.png) Soenneker.Sets.Concurrent.SlidingWindow\n\n### A high-throughput, thread-safe set whose bucketed entries automatically expire after a fixed time window.\n\n`Soenneker.Sets.Concurrent.SlidingWindow` provides a **concurrent sliding-window set** for .NET.\nItems added to the set automatically expire after a configurable time window without requiring manual cleanup.\n\nThe implementation is optimized for **high-concurrency workloads** and avoids expensive per-item timers by using a **bucketed time-slice rotation system**.\n\nThis makes it ideal for **deduplication, rate limiting, and recent activity tracking**.\n\n---\n\n# Installation\n\n```bash\ndotnet add package Soenneker.Sets.Concurrent.SlidingWindow\n```\n\n---\n\n# Why this library exists\n\nMany systems need to track items that should only exist for a **limited period of time**, such as:\n\n* recently processed messages\n* request IDs\n* phone numbers\n* event IDs\n* authentication tokens\n\nTraditional options have downsides:\n\n| Approach                 | Problem                                    |\n| ------------------------ | ------------------------------------------ |\n| `ConcurrentDictionary`   | Requires manual expiration                 |\n| `MemoryCache`            | Heavy and feature-rich for simple tracking |\n| Per-item timers          | Extremely expensive at scale               |\n| Background cleanup scans | High CPU cost                              |\n\n`SlidingWindowConcurrentSet` solves this by using **bucketed time slices** where items automatically expire when their time window passes.\n\n---\n\n# Key Features\n\n✔ High-throughput concurrent operations\n\n✔ Automatic expiration of entries\n\n✔ Sliding window time-based retention\n\n✔ Lock-minimized design\n\n✔ Low allocation footprint\n\n✔ No per-item timers\n\n✔ Safe for heavy multi-threaded workloads\n\n---\n\n# Example\n\n```csharp\nusing Soenneker.Sets.Concurrent.SlidingWindow;\n\nvar set = new SlidingWindowConcurrentSet\u003cstring\u003e(\n    window: TimeSpan.FromMinutes(5),\n    rotationInterval: TimeSpan.FromSeconds(30)\n);\n\nset.TryAdd(\"alpha\");\n\nbool exists = set.Contains(\"alpha\");\n\nawait Task.Delay(TimeSpan.FromMinutes(6));\n\nbool expired = set.Contains(\"alpha\"); // false\n```\n\n---\n\n# Configuration\n\n```csharp\nvar set = new SlidingWindowConcurrentSet\u003cstring\u003e(\n    window: TimeSpan.FromMinutes(10),\n    rotationInterval: TimeSpan.FromSeconds(15)\n);\n```\n\n| Parameter          | Description                                       |\n| ------------------ | ------------------------------------------------- |\n| `window`           | Total time items remain valid                     |\n| `rotationInterval` | Time slice size used for bucket rotation          |\n| `capacityHint`     | Initial capacity hint for the internal dictionary |\n| `comparer`         | Optional equality comparer                        |\n\nThe window is internally divided into **time buckets**:\n\n```\nwindow / rotationInterval = number of buckets\n```\n\nExample:\n\n```\nwindow = 5 minutes\nrotationInterval = 30 seconds\nbucket count = 10\n```\n\nEach rotation advances the window and expires the oldest bucket.\n\n---\n\n# How it works\n\nThe set maintains:\n\n* a **ConcurrentDictionary** for fast lookup\n* a **ring buffer of queues** representing time buckets\n\nWhen an item is added:\n\n1. It is assigned the **current time bucket**\n2. The value is queued in that bucket\n3. The dictionary records the bucket index\n\nA background rotation process periodically:\n\n1. Advances the active bucket\n2. Processes the expired bucket\n3. Removes items whose last activity falls outside the sliding window\n\nThis avoids scanning the entire collection.\n\n---\n\n# Performance Characteristics\n\n| Operation   | Complexity                             |\n| ----------- | -------------------------------------- |\n| `TryAdd`    | O(1)                                   |\n| `Contains`  | O(1)                                   |\n| `TryRemove` | O(1)                                   |\n| Expiration  | O(n) only for items in expiring bucket |\n\nThe design ensures:\n\n* predictable memory usage\n* minimal locking\n* bounded cleanup work\n\n---\n\n# Thread Safety\n\nAll operations are **thread-safe**.\n\nThe set is designed for **high-concurrency environments** and does not require external synchronization.\n\n---\n\n# Disposal\n\nThe set uses an internal **rotation task** driven by `PeriodicTimer`.\n\nWhen the set is no longer needed, it should be disposed:\n\n```csharp\nset.Dispose();\n```\n\nor\n\n```csharp\nawait set.DisposeAsync();\n```\n\nThis stops the internal rotation loop.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoenneker%2Fsoenneker.sets.concurrent.slidingwindow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsoenneker%2Fsoenneker.sets.concurrent.slidingwindow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoenneker%2Fsoenneker.sets.concurrent.slidingwindow/lists"}