Python Resource Manager
This challenge focuses on creating a robust resource management system in Python. You'll build a class that can allocate and deallocate various types of resources, ensuring that no resource is over-allocated and that resources are properly released when no longer needed. This is a fundamental concept in concurrent programming and system design.
Problem Description
Your task is to implement a ResourceManager class in Python. This class should be capable of managing a collection of different resource types, each with a specific quantity available. The manager should provide methods to acquire a certain number of units of a specific resource and to release previously acquired units.
Key Requirements:
- Initialization: The
ResourceManagershould be initialized with a dictionary where keys are resource names (strings) and values are the initial quantities (integers) of each resource available. - Acquire Resource: A method
acquire(resource_name, quantity)should be implemented.- If the requested
quantityof theresource_nameis available, decrement the available quantity and returnTrue. - If the requested
quantityis not available (either the resource doesn't exist or there aren't enough units), returnFalse. - Attempting to acquire a non-existent resource should return
False. - Attempting to acquire a negative quantity should raise a
ValueError.
- If the requested
- Release Resource: A method
release(resource_name, quantity)should be implemented.- Increment the available quantity of the
resource_nameby the givenquantity. - If the
resource_namedoes not exist, this operation should be ignored or log a warning (for this challenge, ignoring is sufficient). - Attempting to release a negative quantity should raise a
ValueError. - Releasing more units than were ever available for a resource should be handled gracefully; the quantity should simply be capped at the maximum initial quantity if you track it, or simply incremented. For this challenge, simply incrementing is acceptable.
- Increment the available quantity of the
- Check Availability: An optional but recommended method
check_availability(resource_name, quantity)could returnTrueifquantityunits ofresource_nameare available, andFalseotherwise. This can be useful for clients before attempting to acquire. - Concurrency Safety (Consideration): While the core implementation may not require explicit locking for this challenge, be mindful that in a real-world concurrent application, these operations would need to be thread-safe. You don't need to implement locks unless specifically asked to.
Expected Behavior:
The manager should accurately track the available quantity of each resource. Acquisitions should only succeed if sufficient resources are present. Releases should correctly restore resources to the pool.
Edge Cases to Consider:
- Acquiring zero quantity.
- Releasing zero quantity.
- Acquiring or releasing from a resource that was not initially defined.
- Attempting to acquire more than the total initial quantity of a resource.
Examples
Example 1:
# Initializing the resource manager
initial_resources = {"CPU": 10, "Memory": 1024}
manager = ResourceManager(initial_resources)
# Attempting to acquire resources
print(manager.acquire("CPU", 2)) # Output: True
print(manager.acquire("Memory", 500)) # Output: True
print(manager.acquire("GPU", 1)) # Output: False (GPU doesn't exist)
print(manager.acquire("CPU", 9)) # Output: False (Only 8 CPU units left)
# Releasing resources
manager.release("CPU", 1)
manager.release("Memory", 200)
# Checking availability (if implemented)
# print(manager.check_availability("CPU", 8)) # Expected: True
# Trying to acquire after release
print(manager.acquire("CPU", 8)) # Output: True
Explanation: The manager starts with 10 CPUs and 1024 MB of Memory. We successfully acquire 2 CPUs and 500 MB of Memory. Attempting to acquire a non-existent GPU fails. Requesting 9 CPUs fails because only 8 are left. Releasing 1 CPU and 200 MB makes them available again. Finally, we can acquire the remaining 8 CPUs.
Example 2:
initial_resources = {"Disk": 50}
manager = ResourceManager(initial_resources)
# Releasing more than available initially (graceful handling)
manager.release("Disk", 100)
print(manager.acquire("Disk", 60)) # Output: True (assuming it can go above initial if not capped)
print(manager.acquire("Disk", 60)) # Output: False
# Invalid quantities
try:
manager.acquire("Disk", -5)
except ValueError as e:
print(e) # Output: Cannot acquire negative quantity.
try:
manager.release("Disk", -10)
except ValueError as e:
print(e) # Output: Cannot release negative quantity.
Explanation: We start with 50 Disks. Releasing 100 disks means we now have 150 (50 + 100) available. We can then acquire 60, leaving 90. Trying to acquire another 60 fails as only 30 are left. The examples also demonstrate the expected ValueError for negative acquisition/release attempts.
Constraints
- Resource names are case-sensitive strings.
- Resource quantities are non-negative integers.
- The number of different resource types can be up to 50.
- The initial quantity of any resource can be up to 1,000,000.
- Acquisition and release quantities will also be within reasonable integer limits.
- Your implementation should be efficient enough to handle a large number of concurrent
acquireandreleasecalls if it were deployed in a multithreaded environment (though explicit thread safety is not required for this specific problem).
Notes
- Consider how you will store the available quantities. A dictionary is a natural fit.
- Think about how to handle operations on resources that were not part of the initial configuration.
- The
check_availabilitymethod is a good addition for usability but not strictly mandatory for a basic solution. - For this problem, focus on the logic of resource tracking and allocation/deallocation. If you were building this for production, you'd need to add thread-safety mechanisms like locks.