diff --git a/solutions/golang/concertticketbookingsystem/booking.go b/solutions/golang/concertticketbookingsystem/booking.go index 80725b44..ef52b57a 100644 --- a/solutions/golang/concertticketbookingsystem/booking.go +++ b/solutions/golang/concertticketbookingsystem/booking.go @@ -1,5 +1,7 @@ package concertbookingsystem +import "fmt" + type Booking struct { ID string User *User @@ -21,11 +23,19 @@ func NewBooking(id string, user *User, concert *Concert, seats []*Seat) *Booking } } -func (b *Booking) ConfirmBooking() { +func (b *Booking) ConfirmBooking() error { if b.Status == BookingStatusPending { b.Status = BookingStatusConfirmed // TODO: Send booking confirmation to user + + for _, seat := range b.Seats { + if seat.status == StatusBooked { + return NewSeatNotAvailableError(fmt.Sprintf("Seat %s is already booked", seat.ID)) + } + seat.status = StatusBooked + } } + return nil } func (b *Booking) CancelBooking() { diff --git a/solutions/golang/concertticketbookingsystem/concert.go b/solutions/golang/concertticketbookingsystem/concert.go index 112a2911..9ac9687d 100644 --- a/solutions/golang/concertticketbookingsystem/concert.go +++ b/solutions/golang/concertticketbookingsystem/concert.go @@ -3,19 +3,21 @@ package concertbookingsystem import "time" type Concert struct { - ID string - Artist string - Venue string - DateTime time.Time - Seats []*Seat + ID string + Artist string + Venue string + DateTime time.Time + Seats []*Seat + LockManager *SeatLockManager } func NewConcert(id, artist, venue string, dateTime time.Time, seats []*Seat) *Concert { return &Concert{ - ID: id, - Artist: artist, - Venue: venue, - DateTime: dateTime, - Seats: seats, + ID: id, + Artist: artist, + Venue: venue, + DateTime: dateTime, + Seats: seats, + LockManager: NewSeatLockManager(), } } diff --git a/solutions/golang/concertticketbookingsystem/concert_booking_system.go b/solutions/golang/concertticketbookingsystem/concert_booking_system.go index 6c9235db..3eb5d9ac 100644 --- a/solutions/golang/concertticketbookingsystem/concert_booking_system.go +++ b/solutions/golang/concertticketbookingsystem/concert_booking_system.go @@ -65,9 +65,9 @@ func (bs *ConcertTicketBookingSystem) BookTickets(user *User, concert *Concert, } } - // Book seats + // Hold seats for the user for _, seat := range seats { - if err := seat.Book(); err != nil { + if err := seat.Hold(); err != nil { // Rollback previous bookings for _, s := range seats { if s == seat { @@ -77,6 +77,7 @@ func (bs *ConcertTicketBookingSystem) BookTickets(user *User, concert *Concert, } return nil, err } + concert.LockManager.AddSeatLock(seat, 5*time.Minute) } // Create booking @@ -87,7 +88,15 @@ func (bs *ConcertTicketBookingSystem) BookTickets(user *User, concert *Concert, bs.processPayment(booking) // Confirm booking - booking.ConfirmBooking() + err := booking.ConfirmBooking() + if err != nil { + // Rollback seat bookings to reserved, ensuring failures do not free seats, so they can be retried + for _, seat := range seats { + seat.status = StatusReserved + } + return nil, err + } + bs.bookings[bookingID] = booking fmt.Printf("Booking %s - %d seats booked\n", booking.ID, len(booking.Seats)) @@ -108,3 +117,15 @@ func (bs *ConcertTicketBookingSystem) CancelBooking(bookingID string) { func (bs *ConcertTicketBookingSystem) processPayment(booking *Booking) { // Mock payment processing } + +func (bs *ConcertTicketBookingSystem) StartLockReleaser(concertId string) { + go func() { + ticker := time.NewTicker(time.Second) + for range ticker.C { + concert := bs.concerts[concertId] + if concert != nil { + concert.LockManager.ReleaseExpiredLocks() + } + } + }() +} diff --git a/solutions/golang/concertticketbookingsystem/concert_booking_system_demo.go b/solutions/golang/concertticketbookingsystem/concert_booking_system_demo.go index 79ebd6aa..6a90a6d8 100644 --- a/solutions/golang/concertticketbookingsystem/concert_booking_system_demo.go +++ b/solutions/golang/concertticketbookingsystem/concert_booking_system_demo.go @@ -31,6 +31,7 @@ func Run() { for _, concert := range searchResults { fmt.Printf("Concert: %s at %s\n", concert.Artist, concert.Venue) } + bookingSystem.StartLockReleaser(concert1.ID) // Book tickets selectedSeats1 := concert1.Seats[:3] // Select first 3 seats diff --git a/solutions/golang/concertticketbookingsystem/seat.go b/solutions/golang/concertticketbookingsystem/seat.go index bb74a6b4..386495d6 100644 --- a/solutions/golang/concertticketbookingsystem/seat.go +++ b/solutions/golang/concertticketbookingsystem/seat.go @@ -1,6 +1,10 @@ package concertbookingsystem -import "sync" +import ( + "fmt" + "sync" + "time" +) type Seat struct { ID string @@ -9,6 +13,7 @@ type Seat struct { Price float64 status SeatStatus mu sync.Mutex + LockUntil time.Time } func NewSeat(id, seatNumber string, seatType SeatType, price float64) *Seat { @@ -21,6 +26,16 @@ func NewSeat(id, seatNumber string, seatType SeatType, price float64) *Seat { } } +func (s *Seat) Hold() error { + s.mu.Lock() + defer s.mu.Unlock() + if s.status != StatusAvailable { + return NewSeatNotAvailableError(fmt.Sprintf("SeatNo: %s Not Available ", s.ID)) + } + s.status = StatusReserved + return nil +} + func (s *Seat) Book() error { s.mu.Lock() defer s.mu.Unlock() @@ -36,9 +51,8 @@ func (s *Seat) Release() { s.mu.Lock() defer s.mu.Unlock() - if s.status == StatusBooked { - s.status = StatusAvailable - } + s.status = StatusAvailable + } func (s *Seat) GetStatus() SeatStatus { diff --git a/solutions/golang/concertticketbookingsystem/seat_lock_manager.go b/solutions/golang/concertticketbookingsystem/seat_lock_manager.go new file mode 100644 index 00000000..f21ea87b --- /dev/null +++ b/solutions/golang/concertticketbookingsystem/seat_lock_manager.go @@ -0,0 +1,76 @@ +package concertbookingsystem + +import ( + "container/heap" + "sync" + "time" +) + +type SeatLockInfo struct { + Seat *Seat + ExpiryTime time.Time + index int +} + +type SeatLockMinHeap []*SeatLockInfo + +func (h SeatLockMinHeap) Len() int { return len(h) } + +func (h SeatLockMinHeap) Less(i, j int) bool { return (h)[i].ExpiryTime.Before((h)[j].ExpiryTime) } + +func (h SeatLockMinHeap) Swap(i, j int) { + (h)[i], (h)[j] = (h)[j], (h)[i] + (h)[i].index = i + (h)[j].index = j +} + +func (h *SeatLockMinHeap) Push(x interface{}) { + n := len(*h) + item := x.(*SeatLockInfo) + item.index = n + *h = append(*h, item) +} + +func (h *SeatLockMinHeap) Pop() interface{} { + prev := *h + n := len(prev) + item := prev[n-1] + item.index = -1 + *h = prev[0 : n-1] + return item +} + +type SeatLockManager struct { + heap SeatLockMinHeap + mu sync.Mutex +} + +func NewSeatLockManager() *SeatLockManager { + return &SeatLockManager{ + heap: make(SeatLockMinHeap, 0), + } +} + +func (slm *SeatLockManager) AddSeatLock(seat *Seat, duration time.Duration) { + slm.mu.Lock() + defer slm.mu.Unlock() + lockInfo := &SeatLockInfo{ + Seat: seat, + ExpiryTime: time.Now().Add(duration), + } + heap.Push(&slm.heap, lockInfo) +} + +func (slm *SeatLockManager) ReleaseExpiredLocks() { + slm.mu.Lock() + defer slm.mu.Unlock() + now := time.Now() + for slm.heap.Len() > 0 { + lockInfo := slm.heap[0] + if lockInfo.ExpiryTime.After(now) { + break + } + heap.Pop(&slm.heap) + lockInfo.Seat.Release() + } +}