Hone logo
Hone
Problems

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:

  1. Initialization: The ResourceManager should be initialized with a dictionary where keys are resource names (strings) and values are the initial quantities (integers) of each resource available.
  2. Acquire Resource: A method acquire(resource_name, quantity) should be implemented.
    • If the requested quantity of the resource_name is available, decrement the available quantity and return True.
    • If the requested quantity is not available (either the resource doesn't exist or there aren't enough units), return False.
    • Attempting to acquire a non-existent resource should return False.
    • Attempting to acquire a negative quantity should raise a ValueError.
  3. Release Resource: A method release(resource_name, quantity) should be implemented.
    • Increment the available quantity of the resource_name by the given quantity.
    • If the resource_name does 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.
  4. Check Availability: An optional but recommended method check_availability(resource_name, quantity) could return True if quantity units of resource_name are available, and False otherwise. This can be useful for clients before attempting to acquire.
  5. 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 acquire and release calls 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_availability method 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.
Loading editor...
python