Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion solutions/golang/concertticketbookingsystem/booking.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package concertbookingsystem

import "fmt"

type Booking struct {
ID string
User *User
Expand All @@ -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() {
Expand Down
22 changes: 12 additions & 10 deletions solutions/golang/concertticketbookingsystem/concert.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -77,6 +77,7 @@ func (bs *ConcertTicketBookingSystem) BookTickets(user *User, concert *Concert,
}
return nil, err
}
concert.LockManager.AddSeatLock(seat, 5*time.Minute)
}

// Create booking
Expand All @@ -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))
Expand All @@ -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()
}
}
}()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 18 additions & 4 deletions solutions/golang/concertticketbookingsystem/seat.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package concertbookingsystem

import "sync"
import (
"fmt"
"sync"
"time"
)

type Seat struct {
ID string
Expand All @@ -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 {
Expand All @@ -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()
Expand All @@ -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 {
Expand Down
76 changes: 76 additions & 0 deletions solutions/golang/concertticketbookingsystem/seat_lock_manager.go
Original file line number Diff line number Diff line change
@@ -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()
}
}