Distributed Transaction Coordinator in Python
Distributed transactions are crucial for maintaining data consistency across multiple services or databases. This challenge asks you to implement a simplified distributed transaction coordinator using the Two-Phase Commit (2PC) protocol in Python. Successfully completing this challenge will demonstrate your understanding of distributed systems concepts and how to manage complex coordination scenarios.
Problem Description
You are tasked with building a basic distributed transaction coordinator. The coordinator will manage transactions involving multiple "participants" (simulated by functions in this case). The 2PC protocol will be used to ensure atomicity – either all participants commit their changes, or none do.
What needs to be achieved:
- Implement a
TransactionCoordinatorclass that manages distributed transactions. - The coordinator should accept a list of participant functions. Each participant function represents a service or database operation that needs to be part of the transaction.
- The coordinator should initiate the 2PC protocol:
- Phase 1 (Prepare Phase): The coordinator asks each participant if they are ready to commit. Participants respond with either "Ready" or "Abort".
- Phase 2 (Commit/Abort Phase): Based on the responses from the prepare phase, the coordinator decides whether to commit or abort the transaction. If all participants are ready, the coordinator sends a "Commit" command to each participant. Otherwise, it sends an "Abort" command.
- Participant functions should simulate database operations and return "Ready" or "Abort" during the prepare phase, and either commit or abort based on the coordinator's command.
Key Requirements:
- The coordinator must handle cases where one or more participants return "Abort" during the prepare phase.
- The coordinator must ensure that all participants receive the same commit/abort decision.
- The participant functions should be designed to be independent and stateless.
- Error handling should be included to gracefully manage potential failures (e.g., a participant not responding).
Expected Behavior:
- When all participants are ready, the transaction should commit successfully.
- When any participant is not ready, the transaction should abort.
- The coordinator should log the progress of the transaction (prepare phase, commit/abort phase).
Edge Cases to Consider:
- What happens if a participant fails to respond during the prepare phase? (Assume timeout and treat as abort)
- What happens if a participant fails to respond during the commit/abort phase? (Assume the coordinator has already logged the decision and cannot recover)
- How does the coordinator handle an empty list of participants? (Should abort)
Examples
Example 1:
Input: coordinator = TransactionCoordinator(); participant1 = lambda: "Ready"; participant2 = lambda: "Ready"; participants = [participant1, participant2]
Output: "Committed"
Explanation: Both participants are ready, so the transaction commits.
Example 2:
Input: coordinator = TransactionCoordinator(); participant1 = lambda: "Ready"; participant2 = lambda: "Abort"; participants = [participant1, participant2]
Output: "Aborted"
Explanation: One participant is not ready, so the transaction aborts.
Example 3: (Edge Case - Participant Failure)
Input: coordinator = TransactionCoordinator(); participant1 = lambda: "Ready"; participant2 = lambda: TimeoutError; participants = [participant1, participant2]
Output: "Aborted"
Explanation: Participant 2 times out (simulated by TimeoutError), treated as an abort.
Constraints
- The coordinator should be able to handle a maximum of 10 participants.
- Participant functions should take no arguments and return a string ("Ready" or "Abort") or raise an exception (simulating failure).
- The coordinator should log its actions to the console.
- The solution should be reasonably efficient; avoid unnecessary loops or complex data structures. Performance is not the primary focus, but avoid obviously inefficient code.
Notes
- You can simulate participant failures by raising exceptions within the participant functions (e.g.,
TimeoutError,Exception). - Consider using a simple logging mechanism to track the transaction's progress.
- The focus is on implementing the 2PC protocol logic, not on simulating a real distributed system. The participants are represented as simple functions.
- Think about how to handle timeouts when waiting for participant responses. A simple
try...exceptblock with a timeout can be used. - This is a simplified implementation; real-world distributed transaction coordinators are significantly more complex.