// Exercise 2: an object pool for expensive buffers (medium) // // Implement BufferPool (a simplified Microsoft.Extensions.ObjectPool): // - Rent(): returns a pooled StringBuilder if one is available (LIFO), otherwise creates a new one // (count creations in Created). // - Return(sb): clears it (sb.Clear()) and keeps it for reuse, but only if the pool holds fewer than // MaxRetained items and sb.Capacity <= 4096 (oversized builders are dropped to avoid hoarding memory). // - Thread safety: use a ConcurrentBag or a lock. // // Run with: dotnet run (inside a console project). using System; using System.Collections.Concurrent; using System.Text; using System.Threading.Tasks; class BufferPool { public const int MaxRetained = 8; public int Created; public StringBuilder Rent() { Created++; return new StringBuilder(); } // TODO: reuse public void Return(StringBuilder sb) { } // TODO public int Retained => 0; // TODO } class Program { static void Main() { int pass = 0, total = 0; void Check(string name, bool ok) { total++; if (ok) pass++; Console.WriteLine($"{(ok ? "PASS" : "FAIL")}: {name}"); } var pool = new BufferPool(); var a = pool.Rent(); a.Append("hello"); pool.Return(a); var b = pool.Rent(); Check("reuses returned builder", ReferenceEquals(a, b) && pool.Created == 1); Check("returned builder is cleared", b.Length == 0); var big = pool.Rent(); big.Append(new string('x', 10_000)); pool.Return(big); Check("oversized builders are dropped", pool.Retained == 0); for (int i = 0; i < 20; i++) pool.Return(new StringBuilder()); Check("retains at most MaxRetained", pool.Retained == BufferPool.MaxRetained); var p2 = new BufferPool(); Parallel.For(0, 10_000, i => { var sb = p2.Rent(); sb.Append(i); p2.Return(sb); }); Check($"thread-safe reuse (created {p2.Created})", p2.Created <= 64 && p2.Retained <= BufferPool.MaxRetained); Console.WriteLine($"--- {pass}/{total} checks passed ---"); } }