From 7acd4d4eb637adea58ccb15c7b388809b44b91ac Mon Sep 17 00:00:00 2001 From: nitagr Date: Sun, 23 Nov 2025 20:56:31 +0530 Subject: [PATCH 1/3] feat: Time based seat holding and releasing featue --- .../concertticketbookingsystem/booking.go | 12 ++++++- .../concert_booking_system.go | 36 +++++++++++++++++-- .../concert_booking_system_demo.go | 1 + .../golang/concertticketbookingsystem/seat.go | 23 +++++++++--- 4 files changed, 64 insertions(+), 8 deletions(-) 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_booking_system.go b/solutions/golang/concertticketbookingsystem/concert_booking_system.go index 6c9235db..05edfcbd 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(time.Minute); err != nil { // Rollback previous bookings for _, s := range seats { if s == seat { @@ -87,7 +87,16 @@ 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 + } + + // Store booking bs.bookings[bookingID] = booking fmt.Printf("Booking %s - %d seats booked\n", booking.ID, len(booking.Seats)) @@ -108,3 +117,24 @@ 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 { + bs.releaseExpiredSeatLocks(bs.concerts[concertId].Seats) + } + }() +} + +func (bs *ConcertTicketBookingSystem) releaseExpiredSeatLocks(seats []*Seat) { + for _, seat := range seats { + seat.mu.Lock() + + if seat.status == StatusReserved && time.Now().After(seat.LockUntil) { + seat.status = StatusAvailable + fmt.Printf("Release holded seat %s\n", seat.ID) + } + seat.mu.Unlock() + } +} 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..d76da99a 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,17 @@ func NewSeat(id, seatNumber string, seatType SeatType, price float64) *Seat { } } +func (s *Seat) Hold(duration time.Duration) 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 + s.LockUntil = time.Now().Add(duration) + return nil +} + func (s *Seat) Book() error { s.mu.Lock() defer s.mu.Unlock() @@ -36,9 +52,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 { From 448028ddf39a6d4d35d76fa7fe51e6a2de98ab57 Mon Sep 17 00:00:00 2001 From: nitagr Date: Tue, 25 Nov 2025 23:35:27 +0530 Subject: [PATCH 2/3] feat: Min heap based seat locking expiry [# Please enter the commit message for your changes. Lines starting --- .../concertticketbookingsystem/concert.go | 22 +++--- .../concert_booking_system.go | 18 ++--- .../seat_lock_manager.go | 76 +++++++++++++++++++ 3 files changed, 93 insertions(+), 23 deletions(-) create mode 100644 solutions/golang/concertticketbookingsystem/seat_lock_manager.go 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 05edfcbd..46ccdaad 100644 --- a/solutions/golang/concertticketbookingsystem/concert_booking_system.go +++ b/solutions/golang/concertticketbookingsystem/concert_booking_system.go @@ -77,6 +77,7 @@ func (bs *ConcertTicketBookingSystem) BookTickets(user *User, concert *Concert, } return nil, err } + concert.LockManager.AddSeatLock(seat, 5*time.Minute) } // Create booking @@ -122,19 +123,10 @@ func (bs *ConcertTicketBookingSystem) StartLockReleaser(concertId string) { go func() { ticker := time.NewTicker(time.Second) for range ticker.C { - bs.releaseExpiredSeatLocks(bs.concerts[concertId].Seats) + concert := bs.concerts[concertId] + if concert != nil { + concert.LockManager.ReleaseExpiredLocks() + } } }() } - -func (bs *ConcertTicketBookingSystem) releaseExpiredSeatLocks(seats []*Seat) { - for _, seat := range seats { - seat.mu.Lock() - - if seat.status == StatusReserved && time.Now().After(seat.LockUntil) { - seat.status = StatusAvailable - fmt.Printf("Release holded seat %s\n", seat.ID) - } - seat.mu.Unlock() - } -} 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() + } +} From 3887826d25f9dd91915e1aa7fa6c56a10d060eb9 Mon Sep 17 00:00:00 2001 From: nitagr Date: Wed, 26 Nov 2025 00:44:30 +0530 Subject: [PATCH 3/3] remove hold duration as it is already managed in lock manager --- .../concertticketbookingsystem/concert_booking_system.go | 3 +-- solutions/golang/concertticketbookingsystem/seat.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/solutions/golang/concertticketbookingsystem/concert_booking_system.go b/solutions/golang/concertticketbookingsystem/concert_booking_system.go index 46ccdaad..3eb5d9ac 100644 --- a/solutions/golang/concertticketbookingsystem/concert_booking_system.go +++ b/solutions/golang/concertticketbookingsystem/concert_booking_system.go @@ -67,7 +67,7 @@ func (bs *ConcertTicketBookingSystem) BookTickets(user *User, concert *Concert, // Hold seats for the user for _, seat := range seats { - if err := seat.Hold(time.Minute); err != nil { + if err := seat.Hold(); err != nil { // Rollback previous bookings for _, s := range seats { if s == seat { @@ -97,7 +97,6 @@ func (bs *ConcertTicketBookingSystem) BookTickets(user *User, concert *Concert, return nil, err } - // Store booking bs.bookings[bookingID] = booking fmt.Printf("Booking %s - %d seats booked\n", booking.ID, len(booking.Seats)) diff --git a/solutions/golang/concertticketbookingsystem/seat.go b/solutions/golang/concertticketbookingsystem/seat.go index d76da99a..386495d6 100644 --- a/solutions/golang/concertticketbookingsystem/seat.go +++ b/solutions/golang/concertticketbookingsystem/seat.go @@ -26,14 +26,13 @@ func NewSeat(id, seatNumber string, seatType SeatType, price float64) *Seat { } } -func (s *Seat) Hold(duration time.Duration) error { +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 - s.LockUntil = time.Now().Add(duration) return nil }