using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace OrderProcessingSystem
#region Implementation Code
void Log(string message);
public class ConsoleLogger : ILogger
public void Log(string message)
Console.WriteLine($"{DateTime.Now}: {message}");
public Guid OrderId { get; }
public string ProductId { get; }
public int Quantity { get; }
public Order(string productId, int quantity)
OrderId = Guid.NewGuid();
public class OrderProcessor
private readonly ConcurrentDictionary<string, int> _inventory;
private readonly SemaphoreSlim _inventoryLock = new SemaphoreSlim(1, 1);
private readonly ILogger _logger;
private int _successfulOrders = 0;
private int _failedOrders = 0;
private int _deadlocksDetected = 0;
public OrderProcessor(Dictionary<string, int> initialInventory, ILogger logger)
if (initialInventory == null)
throw new ArgumentNullException(nameof(initialInventory), "Initial inventory cannot be null.");
throw new ArgumentNullException(nameof(logger), "Logger cannot be null.");
_inventory = new ConcurrentDictionary<string, int>(initialInventory);
public async Task<(int successful, int failed, int deadlocks)> ProcessOrdersAsync(List<Order> orders)
throw new ArgumentNullException(nameof(orders), "Orders list cannot be null.");
await Parallel.ForEachAsync(orders, async (order, cancellationToken) =>
await ProcessOrderWithRetryAsync(order, maxRetries: 3);
return (_successfulOrders, _failedOrders, _deadlocksDetected);
private async Task<bool> ProcessOrderWithRetryAsync(Order order, int maxRetries)
if (order == null || string.IsNullOrWhiteSpace(order.ProductId) || order.Quantity <= 0)
_logger.Log($"Order {order?.OrderId} has invalid details. Order failed.");
Interlocked.Increment(ref _failedOrders);
while (attempt <= maxRetries)
bool processed = await ProcessOrderAsync(order);
Interlocked.Increment(ref _successfulOrders);
if (attempt <= maxRetries)
TimeSpan delay = CalculateRetryDelay(attempt);
_logger.Log($"Retrying order {order.OrderId} (attempt {attempt}) after {delay.TotalMilliseconds} ms delay.");
_logger.Log($"Order {order.OrderId} failed after {maxRetries} retries.");
Interlocked.Increment(ref _failedOrders);
private async Task<bool> ProcessOrderAsync(Order order)
Task lockTask = _inventoryLock.WaitAsync();
Task timeoutTask = Task.Delay(5000);
Task completedTask = await Task.WhenAny(lockTask, timeoutTask);
if (completedTask == timeoutTask)
Interlocked.Increment(ref _deadlocksDetected);
_logger.Log($"Deadlock detected for order {order.OrderId} on product {order.ProductId}.");
if (!_inventory.ContainsKey(order.ProductId))
_logger.Log($"Product {order.ProductId} not found for order {order.OrderId}.");
int currentStock = _inventory[order.ProductId];
if (currentStock < order.Quantity)
_logger.Log($"Insufficient inventory for order {order.OrderId}: Product {order.ProductId}, Requested {order.Quantity}, Available {currentStock}.");
bool updated = _inventory.TryUpdate(order.ProductId, currentStock - order.Quantity, currentStock);
_logger.Log($"Race condition detected while processing order {order.OrderId} for product {order.ProductId}.");
_logger.Log($"Order {order.OrderId} processed successfully: Product {order.ProductId}, Quantity {order.Quantity}. New stock: {_inventory[order.ProductId]}.");
_logger.Log($"Exception processing order {order.OrderId}: {ex.Message}");
_inventoryLock.Release();
private TimeSpan CalculateRetryDelay(int attempt)
return TimeSpan.FromMilliseconds(100 * Math.Pow(2, attempt));
public Dictionary<string, int> GetInventorySnapshot()
return new Dictionary<string, int>(_inventory);
public (int successful, int failed, int deadlocks) GetMonitoringStatistics()
return (_successfulOrders, _failedOrders, _deadlocksDetected);
public class OrderProcessorTests
public async Task Test_ProcessOrdersAsync_NullOrdersList_ThrowsArgumentNullException()
var initialInventory = new Dictionary<string, int> { { "A", 10 } };
ILogger logger = new TestLogger();
var processor = new OrderProcessor(initialInventory, logger);
await processor.ProcessOrdersAsync(null);
Console.WriteLine("FAIL: Expected ArgumentNullException was not thrown.");
catch (ArgumentNullException)
Console.WriteLine("PASS: ProcessOrdersAsync_NullOrdersList_ThrowsArgumentNullException");
public async Task Test_ProcessOrdersAsync_EmptyOrdersList_ReturnsZeroStatistics()
var initialInventory = new Dictionary<string, int> { { "A", 10 } };
ILogger logger = new TestLogger();
var processor = new OrderProcessor(initialInventory, logger);
var result = await processor.ProcessOrdersAsync(new List<Order>());
if (result.successful == 0 && result.failed == 0 && result.deadlocks == 0)
Console.WriteLine("PASS: ProcessOrdersAsync_EmptyOrdersList_ReturnsZeroStatistics");
Console.WriteLine("FAIL: ProcessOrdersAsync_EmptyOrdersList_ReturnsZeroStatistics");
public async Task Test_ProcessOrdersAsync_SufficientInventory_ProcessesOrdersSuccessfully()
var initialInventory = new Dictionary<string, int> { { "A", 10 }, { "B", 5 } };
ILogger logger = new TestLogger();
var processor = new OrderProcessor(initialInventory, logger);
var orders = new List<Order>
(int successful, int failed, int deadlocks) = await processor.ProcessOrdersAsync(orders);
var snapshot = processor.GetInventorySnapshot();
if (successful == 2 && failed == 0 && deadlocks == 0 &&
snapshot["A"] == 7 && snapshot["B"] == 3)
Console.WriteLine("PASS: ProcessOrdersAsync_SufficientInventory_ProcessesOrdersSuccessfully");
Console.WriteLine("FAIL: ProcessOrdersAsync_SufficientInventory_ProcessesOrdersSuccessfully");
public async Task Test_ProcessOrdersAsync_InsufficientInventory_OrderFailsAfterRetries()
var initialInventory = new Dictionary<string, int> { { "A", 2 } };
ILogger logger = new TestLogger();
var processor = new OrderProcessor(initialInventory, logger);
var orders = new List<Order> { new Order("A", 3) };
(int successful, int failed, int deadlocks) = await processor.ProcessOrdersAsync(orders);
var snapshot = processor.GetInventorySnapshot();
if (successful == 0 && failed == 1 && deadlocks == 0 && snapshot["A"] == 2)
Console.WriteLine("PASS: ProcessOrdersAsync_InsufficientInventory_OrderFailsAfterRetries");
Console.WriteLine("FAIL: ProcessOrdersAsync_InsufficientInventory_OrderFailsAfterRetries");
public async Task Test_ProcessOrdersAsync_MixedSufficientAndInsufficientInventory_ProcessesSomeOrders()
var initialInventory = new Dictionary<string, int> { { "A", 5 }, { "B", 10 } };
ILogger logger = new TestLogger();
var processor = new OrderProcessor(initialInventory, logger);
var orders = new List<Order>
(int successful, int failed, int deadlocks) = await processor.ProcessOrdersAsync(orders);
var snapshot = processor.GetInventorySnapshot();
if (successful == 1 && failed == 1 && deadlocks == 0 &&
snapshot["A"] == 2 && snapshot["B"] == 10)
Console.WriteLine("PASS: ProcessOrdersAsync_MixedSufficientAndInsufficientInventory_ProcessesSomeOrders");
Console.WriteLine("FAIL: ProcessOrdersAsync_MixedSufficientAndInsufficientInventory_ProcessesSomeOrders");
public async Task Test_ProcessOrdersAsync_ZeroQuantityOrder_FailsImmediately()
var initialInventory = new Dictionary<string, int> { { "A", 5 } };
ILogger logger = new TestLogger();
var processor = new OrderProcessor(initialInventory, logger);
var orders = new List<Order> { new Order("A", 0) };
(int successful, int failed, int deadlocks) = await processor.ProcessOrdersAsync(orders);
var snapshot = processor.GetInventorySnapshot();
if (successful == 0 && failed == 1 && deadlocks == 0 && snapshot["A"] == 5)
Console.WriteLine("PASS: ProcessOrdersAsync_ZeroQuantityOrder_FailsImmediately");
Console.WriteLine("FAIL: ProcessOrdersAsync_ZeroQuantityOrder_FailsImmediately");
public async Task Test_ProcessOrdersAsync_NegativeQuantityOrder_FailsImmediately()
var initialInventory = new Dictionary<string, int> { { "A", 5 } };
ILogger logger = new TestLogger();
var processor = new OrderProcessor(initialInventory, logger);
var orders = new List<Order> { new Order("A", -1) };
(int successful, int failed, int deadlocks) = await processor.ProcessOrdersAsync(orders);
var snapshot = processor.GetInventorySnapshot();
if (successful == 0 && failed == 1 && deadlocks == 0 && snapshot["A"] == 5)
Console.WriteLine("PASS: ProcessOrdersAsync_NegativeQuantityOrder_FailsImmediately");
Console.WriteLine("FAIL: ProcessOrdersAsync_NegativeQuantityOrder_FailsImmediately");
public async Task Test_ProcessOrdersAsync_NullProductId_FailsImmediately()
var initialInventory = new Dictionary<string, int> { { "A", 5 } };
ILogger logger = new TestLogger();
var processor = new OrderProcessor(initialInventory, logger);
var orders = new List<Order> { new Order(null, 1) };
(int successful, int failed, int deadlocks) = await processor.ProcessOrdersAsync(orders);
var snapshot = processor.GetInventorySnapshot();
if (successful == 0 && failed == 1 && deadlocks == 0 && snapshot["A"] == 5)
Console.WriteLine("PASS: ProcessOrdersAsync_NullProductId_FailsImmediately");
Console.WriteLine("FAIL: ProcessOrdersAsync_NullProductId_FailsImmediately");
public async Task Test_ProcessOrdersAsync_EmptyProductId_FailsImmediately()
var initialInventory = new Dictionary<string, int> { { "A", 5 } };
ILogger logger = new TestLogger();
var processor = new OrderProcessor(initialInventory, logger);
var orders = new List<Order> { new Order("", 1) };
(int successful, int failed, int deadlocks) = await processor.ProcessOrdersAsync(orders);
var snapshot = processor.GetInventorySnapshot();
if (successful == 0 && failed == 1 && deadlocks == 0 && snapshot["A"] == 5)
Console.WriteLine("PASS: ProcessOrdersAsync_EmptyProductId_FailsImmediately");
Console.WriteLine("FAIL: ProcessOrdersAsync_EmptyProductId_FailsImmediately");
public async Task Test_OrderProcessor_NonExistentProduct_OrderFailsImmediately()
var initialInventory = new Dictionary<string, int> { { "A", 5 } };
ILogger logger = new TestLogger();
var processor = new OrderProcessor(initialInventory, logger);
var orders = new List<Order> { new Order("B", 1) };
(int successful, int failed, int deadlocks) = await processor.ProcessOrdersAsync(orders);
var snapshot = processor.GetInventorySnapshot();
if (successful == 0 && failed == 1 && deadlocks == 0 && snapshot["A"] == 5)
Console.WriteLine("PASS: OrderProcessor_NonExistentProduct_OrderFailsImmediately");
Console.WriteLine("FAIL: OrderProcessor_NonExistentProduct_OrderFailsImmediately");
public void Test_OrderProcessor_NullInitialInventory_ThrowsArgumentNullException()
ILogger logger = new TestLogger();
var processor = new OrderProcessor(null, logger);
Console.WriteLine("FAIL: Expected ArgumentNullException for null initial inventory not thrown.");
catch (ArgumentNullException)
Console.WriteLine("PASS: OrderProcessor_NullInitialInventory_ThrowsArgumentNullException");
public void Test_OrderProcessor_NullLogger_ThrowsArgumentNullException()
var initialInventory = new Dictionary<string, int> { { "A", 5 } };
var processor = new OrderProcessor(initialInventory, null);
Console.WriteLine("FAIL: Expected ArgumentNullException for null logger not thrown.");
catch (ArgumentNullException)
Console.WriteLine("PASS: OrderProcessor_NullLogger_ThrowsArgumentNullException");
public class TestLogger : ILogger
public List<string> Logs { get; } = new List<string>();
public void Log(string message)
#region Program Entry Point
public static async Task Main(string[] args)
Console.WriteLine("Running Unit Tests...\n");
var tests = new OrderProcessorTests();
await tests.Test_ProcessOrdersAsync_NullOrdersList_ThrowsArgumentNullException();
await tests.Test_ProcessOrdersAsync_EmptyOrdersList_ReturnsZeroStatistics();
await tests.Test_ProcessOrdersAsync_SufficientInventory_ProcessesOrdersSuccessfully();
await tests.Test_ProcessOrdersAsync_InsufficientInventory_OrderFailsAfterRetries();
await tests.Test_ProcessOrdersAsync_MixedSufficientAndInsufficientInventory_ProcessesSomeOrders();
await tests.Test_ProcessOrdersAsync_ZeroQuantityOrder_FailsImmediately();
await tests.Test_ProcessOrdersAsync_NegativeQuantityOrder_FailsImmediately();
await tests.Test_ProcessOrdersAsync_NullProductId_FailsImmediately();
await tests.Test_ProcessOrdersAsync_EmptyProductId_FailsImmediately();
await tests.Test_OrderProcessor_NonExistentProduct_OrderFailsImmediately();
tests.Test_OrderProcessor_NullInitialInventory_ThrowsArgumentNullException();
tests.Test_OrderProcessor_NullLogger_ThrowsArgumentNullException();
Console.WriteLine("\nAll unit tests executed.");