// parseWait is used to parse the ?wait and ?index query params // Returns true on error func parseWait(resp http.ResponseWriter, req *http.Request, b *structs.QueryOptions) bool { query := req.URL.Query() if wait := query.Get("wait"); wait != "" { dur, err := time.ParseDuration(wait) if err != nil { resp.WriteHeader(400) resp.Write([]byte("Invalid wait time")) return true } b.MaxQueryTime = dur } if idx := query.Get("index"); idx != "" { index, err := strconv.ParseUint(idx, 10, 64) if err != nil { resp.WriteHeader(400) resp.Write([]byte("Invalid index")) return true } b.MinQueryIndex = index } return false }
// blockingRPC is used for queries that need to wait for a // minimum index. This is used to block and wait for changes. func (s *Server) blockingRPC(b *structs.QueryOptions, m *structs.QueryMeta, tables MDBTables, run func() error) error { var timeout <-chan time.Time var notifyCh chan struct{} // Fast path non-blocking if b.MinQueryIndex == 0 { goto RUN_QUERY } // Sanity check that we have tables to block on if len(tables) == 0 { panic("no tables to block on") } // Restrict the max query time if b.MaxQueryTime > maxQueryTime { b.MaxQueryTime = maxQueryTime } // Ensure a time limit is set if we have an index if b.MinQueryIndex > 0 && b.MaxQueryTime == 0 { b.MaxQueryTime = maxQueryTime } // Setup a query timeout if b.MaxQueryTime > 0 { timeout = time.After(b.MaxQueryTime) } // Setup a notification channel for changes SETUP_NOTIFY: if b.MinQueryIndex > 0 { notifyCh = make(chan struct{}, 1) s.fsm.State().Watch(tables, notifyCh) } RUN_QUERY: // Update the query meta data s.setQueryMeta(m) // Check if query must be consistent if b.RequireConsistent { if err := s.consistentRead(); err != nil { return err } } // Run the query function err := run() // Check for minimum query time if err == nil && m.Index > 0 && m.Index <= b.MinQueryIndex { select { case <-notifyCh: goto SETUP_NOTIFY case <-timeout: } } return err }
// EventList is used to retrieve the recent list of events func (s *HTTPServer) EventList(resp http.ResponseWriter, req *http.Request) (interface{}, error) { // Parse the query options, since we simulate a blocking query var b structs.QueryOptions if parseWait(resp, req, &b) { return nil, nil } // Look for a name filter var nameFilter string if filt := req.URL.Query().Get("name"); filt != "" { nameFilter = filt } // Lots of this logic is borrowed from consul/rpc.go:blockingRPC // However we cannot use that directly since this code has some // slight semantics differences... var timeout <-chan time.Time var notifyCh chan struct{} // Fast path non-blocking if b.MinQueryIndex == 0 { goto RUN_QUERY } // Restrict the max query time if b.MaxQueryTime > maxQueryTime { b.MaxQueryTime = maxQueryTime } // Ensure a time limit is set if we have an index if b.MinQueryIndex > 0 && b.MaxQueryTime == 0 { b.MaxQueryTime = maxQueryTime } // Setup a query timeout if b.MaxQueryTime > 0 { timeout = time.After(b.MaxQueryTime) } // Setup a notification channel for changes SETUP_NOTIFY: if b.MinQueryIndex > 0 { notifyCh = make(chan struct{}, 1) s.agent.eventNotify.Wait(notifyCh) } RUN_QUERY: // Get the recent events events := s.agent.UserEvents() // Filter the events if necessary if nameFilter != "" { n := len(events) for i := 0; i < n; i++ { if events[i].Name == nameFilter { continue } events[i], events[n-1] = events[n-1], nil i-- n-- } events = events[:n] } // Determine the index var index uint64 if len(events) == 0 { // Return a non-zero index to prevent a hot query loop. This // can be caused by a watch for example when there is no matching // events. index = 1 } else { last := events[len(events)-1] index = uuidToUint64(last.ID) } setIndex(resp, index) // Check for exact match on the query value. Because // the index value is not monotonic, we just ensure it is // not an exact match. if index > 0 && index == b.MinQueryIndex { select { case <-notifyCh: goto SETUP_NOTIFY case <-timeout: } } return events, nil }
// blockingRPC is used for queries that need to wait for a minimum index. This // is used to block and wait for changes. func (s *Server) blockingRPC(queryOpts *structs.QueryOptions, queryMeta *structs.QueryMeta, watch state.Watch, run func() error) error { var timeout *time.Timer var notifyCh chan struct{} // Fast path right to the non-blocking query. if queryOpts.MinQueryIndex == 0 { goto RUN_QUERY } // Make sure a watch was given if we were asked to block. if watch == nil { panic("no watch given for blocking query") } // Restrict the max query time, and ensure there is always one. if queryOpts.MaxQueryTime > maxQueryTime { queryOpts.MaxQueryTime = maxQueryTime } else if queryOpts.MaxQueryTime <= 0 { queryOpts.MaxQueryTime = defaultQueryTime } // Apply a small amount of jitter to the request. queryOpts.MaxQueryTime += lib.RandomStagger(queryOpts.MaxQueryTime / jitterFraction) // Setup a query timeout. timeout = time.NewTimer(queryOpts.MaxQueryTime) // Setup the notify channel. notifyCh = make(chan struct{}, 1) // Ensure we tear down any watches on return. defer func() { timeout.Stop() watch.Clear(notifyCh) }() REGISTER_NOTIFY: // Register the notification channel. This may be done multiple times if // we haven't reached the target wait index. watch.Wait(notifyCh) RUN_QUERY: // Update the query metadata. s.setQueryMeta(queryMeta) // If the read must be consistent we verify that we are still the leader. if queryOpts.RequireConsistent { if err := s.consistentRead(); err != nil { return err } } // Run the query. metrics.IncrCounter([]string{"consul", "rpc", "query"}, 1) err := run() // Check for minimum query time. if err == nil && queryMeta.Index > 0 && queryMeta.Index <= queryOpts.MinQueryIndex { select { case <-notifyCh: goto REGISTER_NOTIFY case <-timeout.C: } } return err }