package airport import ( "fmt" "math/rand" "time" ) var distantFuture = time.Unix(1<<63-1, 0) // Max value for Unix timestamp type GateNumber int type Gate struct { id GateNumber walkingTimeFromSecurity time.Duration taxiTimeToRunway time.Duration flights []Flight passengerQueues map[FlightNumber]chan passengerGateData baggageQueues map[FlightNumber]chan baggageRequest aircraftQueues map[FlightNumber]chan *Aircraft departSignals map[FlightNumber]chan struct{} } var _ BaggageProcessor = &Gate{} func NewGate(id GateNumber, walkingTimeFromSecurity time.Duration, taxiTimeToRunway time.Duration) Gate { return Gate{ id: id, walkingTimeFromSecurity: walkingTimeFromSecurity, taxiTimeToRunway: taxiTimeToRunway, flights: []Flight{}, passengerQueues: make(map[FlightNumber]chan passengerGateData), baggageQueues: make(map[FlightNumber]chan baggageRequest), aircraftQueues: make(map[FlightNumber]chan *Aircraft), departSignals: make(map[FlightNumber]chan struct{}), } } func NewGates(count int) map[GateNumber]*Gate { gates := make(map[GateNumber]*Gate) for i := range count { walkingTime := time.Duration(5+rand.Intn(6)) * time.Second taxiTime := time.Duration(5+rand.Intn(6)) * time.Second gate := NewGate(GateNumber(i+1), walkingTime, taxiTime) gates[GateNumber(i+1)] = &gate } return gates } func (g *Gate) SetFlights(flights []Flight) { g.flights = flights if g.passengerQueues == nil { g.passengerQueues = make(map[FlightNumber]chan passengerGateData) } if g.baggageQueues == nil { g.baggageQueues = make(map[FlightNumber]chan baggageRequest) } if g.aircraftQueues == nil { g.aircraftQueues = make(map[FlightNumber]chan *Aircraft) } if g.departSignals == nil { g.departSignals = make(map[FlightNumber]chan struct{}) } for _, f := range flights { if _, ok := g.passengerQueues[f.Id]; !ok { g.passengerQueues[f.Id] = make(chan passengerGateData, 16) } if _, ok := g.baggageQueues[f.Id]; !ok { g.baggageQueues[f.Id] = make(chan baggageRequest, 16) } if _, ok := g.aircraftQueues[f.Id]; !ok { g.aircraftQueues[f.Id] = make(chan *Aircraft, 1) } if _, ok := g.departSignals[f.Id]; !ok { g.departSignals[f.Id] = make(chan struct{}, 1) } } } type passengerGateData struct { p Passenger err chan error arrival time.Time } type baggageRequest struct { b Baggage err chan error } // Blocks for the walking distance to the gate + the time until the passenger has boarded func (g *Gate) Process(passenger Passenger) error { if passenger.BoardingPass == nil { return fmt.Errorf("no boarding pass") } if passenger.BoardingPass.Gate != g.id { return fmt.Errorf("invalid gate: %v", passenger.BoardingPass.Gate) } fn := passenger.BoardingPass.FlightNumber q, ok := g.passengerQueues[fn] if !ok { return fmt.Errorf("invalid flight %v", fn) } // find the flight to determine departure time var flight Flight found := false for _, f := range g.flights { if f.Id == fn { flight = f found = true break } } if !found { return fmt.Errorf("invalid flight %v", fn) } errc := make(chan error) // compute intended arrival time (start + walking), record before sleeping intendedArrival := time.Now().Add(g.walkingTimeFromSecurity) fmt.Printf("[gate %d] Process start for %s now=%v intendedArrival=%v departure=%v\n", g.id, passenger.Name, time.Now(), intendedArrival, flight.DepartureTime) time.Sleep(g.walkingTimeFromSecurity) fmt.Printf("[gate %d] after walking for %s now=%v intendedArrival=%v departure=%v\n", g.id, passenger.Name, time.Now(), intendedArrival, flight.DepartureTime) // if already departed, return immediately if intendedArrival.After(flight.DepartureTime) { return fmt.Errorf("flight already departed") } // attempt to enqueue but don't block past departure time data := passengerGateData{p: passenger, err: errc, arrival: intendedArrival} // allow waiting until departure relative to the intended arrival time sendTimeout := flight.DepartureTime.Sub(intendedArrival) if sendTimeout < 0 { sendTimeout = 0 } fmt.Printf("[gate %d] enqueuing passenger %s for flight %s (sendTimeout=%v)\n", g.id, passenger.Name, fn, sendTimeout) select { case q <- data: // sent case <-time.After(sendTimeout): return fmt.Errorf("flight already departed") } res := <-errc fmt.Printf("[gate %d] passenger %s processed with err=%v\n", g.id, passenger.Name, res) return res } // Blocks until departure (includes taxi time to runway) func (g *Gate) ProcessAircraft(ac *Aircraft) { fn := ac.Flight.Id q, ok := g.aircraftQueues[fn] if !ok { // nothing to do return } q <- ac // wait until depart signal if sig, ok := g.departSignals[fn]; ok { <-sig } } func (g *Gate) ProcessBaggage(fn FlightNumber, b Baggage) error { q, ok := g.baggageQueues[fn] if !ok { return fmt.Errorf("invalid flight %v", fn) } // find flight for departure time var flight Flight found := false for _, f := range g.flights { if f.Id == fn { flight = f found = true break } } if !found { return fmt.Errorf("invalid flight %v", fn) } errc := make(chan error) tLeft := time.Until(flight.DepartureTime) if tLeft <= 0 { return fmt.Errorf("flight already departed") } select { case q <- baggageRequest{b: b, err: errc}: // sent case <-time.After(tLeft): return fmt.Errorf("flight already departed") } return <-errc } func (g Gate) Start() { // start a handler for each configured flight for _, f := range g.flights { f := f pCh := g.passengerQueues[f.Id] bCh := g.baggageQueues[f.Id] acCh := g.aircraftQueues[f.Id] depart := g.departSignals[f.Id] go func() { // wait until boarding time fmt.Printf("[gate %d] handler starting for %s: boarding=%v departure=%v now=%v\n", g.id, f.Id, f.BoardingTime, f.DepartureTime, time.Now()) if time.Until(f.BoardingTime) > 0 { time.Sleep(time.Until(f.BoardingTime)) } // boarding open departureTimer := time.NewTimer(time.Until(f.DepartureTime)) waitingPassengers := []passengerGateData{} waitingBaggage := []Baggage{} var aircraft *Aircraft boarding := true for boarding { select { case pg := <-pCh: fmt.Printf("[gate %d] handler received passenger %s, aircraft=%v arrival=%v departure=%v\n", g.id, pg.p.Name, aircraft != nil, pg.arrival, f.DepartureTime) if pg.arrival.After(f.DepartureTime) { pg.err <- fmt.Errorf("flight already departed") continue } if aircraft != nil { if err := aircraft.Process(pg.p); err != nil { pg.err <- err } else { pg.err <- nil } } else { waitingPassengers = append(waitingPassengers, pg) } case bd := <-bCh: fmt.Printf("[gate %d] handler received baggage for %s, aircraft=%v\n", g.id, f.Id, aircraft != nil) if aircraft != nil { if err := aircraft.ProcessBaggage(f.Id, bd.b); err != nil { bd.err <- err } else { bd.err <- nil } } else { waitingBaggage = append(waitingBaggage, bd.b) bd.err <- nil } case ac := <-acCh: fmt.Printf("[gate %d] handler received aircraft %s\n", g.id, ac.Flight.Id) aircraft = ac // deliver waiting passengers for _, wp := range waitingPassengers { if time.Now().After(f.DepartureTime) { wp.err <- fmt.Errorf("flight already departed") continue } if err := aircraft.Process(wp.p); err != nil { wp.err <- err } else { wp.err <- nil } } waitingPassengers = nil // deliver waiting baggage for _, wb := range waitingBaggage { aircraft.ProcessBaggage(f.Id, wb) } waitingBaggage = nil case <-departureTimer.C: fmt.Printf("[gate %d] handler departure timer fired for %s\n", g.id, f.Id) boarding = false } } // boarding closed: drain any buffered messages and process those for { processed := false select { case pg := <-pCh: processed = true if pg.arrival.After(f.DepartureTime) { pg.err <- fmt.Errorf("flight already departed") continue } if aircraft != nil { if err := aircraft.Process(pg.p); err != nil { pg.err <- err } else { pg.err <- nil } } else { waitingPassengers = append(waitingPassengers, pg) } case bd := <-bCh: processed = true if aircraft != nil { if err := aircraft.ProcessBaggage(f.Id, bd.b); err != nil { bd.err <- err } else { bd.err <- nil } } else { waitingBaggage = append(waitingBaggage, bd.b) bd.err <- nil } case ac := <-acCh: processed = true aircraft = ac // deliver waiting passengers for _, wp := range waitingPassengers { if wp.arrival.After(f.DepartureTime) { wp.err <- fmt.Errorf("flight already departed") continue } if err := aircraft.Process(wp.p); err != nil { wp.err <- err } else { wp.err <- nil } } waitingPassengers = nil // deliver waiting baggage for _, wb := range waitingBaggage { aircraft.ProcessBaggage(f.Id, wb) } waitingBaggage = nil default: } if !processed { break } } // notify remaining waiting passengers that the flight is gone for _, wp := range waitingPassengers { wp.err <- fmt.Errorf("flight already departed") } // After boarding closed, wait taxi time then signal departure time.Sleep(g.taxiTimeToRunway) // signal any ProcessAircraft waiting on depart select { case depart <- struct{}{}: default: } close(depart) }() } }