// buildLineBytes is the most bytes of one line in a build input or usage log.
const buildLineBytes = 1 << 20
+// buildWaitTimeout is the longest wait for pending memory builds.
+const buildWaitTimeout = time.Second
+
// buildQuotaLimit is the largest increase in used quota points of a run.
const buildQuotaLimit = 15.0
// waitBuilds waits up to one second without leaving a child context open.
func (r *BuildRun) waitBuilds(ctx context.Context) error {
- ctx, cancel := context.WithTimeout(ctx, time.Second)
+ ctx, cancel := context.WithTimeout(ctx, buildWaitTimeout)
defer cancel()
return r.memory.Wait(ctx)
// scheduleSource is the source of the work events of the runs.
scheduleSource = "schedule"
- // scheduleCheck is the longest wait of Run before it looks at the times
- // again, so that it sees a suspend of the machine or a change of the
- // clock.
- scheduleCheck = time.Minute
+ // scheduleClockWaitTimeout is the longest wait of Run before it looks
+ // at the times again, so that it sees a suspend of the machine or a
+ // change of the clock.
+ scheduleClockWaitTimeout = time.Minute
// scheduleLate is how long after its time a run is late.
scheduleLate = time.Minute
}
// Run starts the runs of the due schedules in l until ctx ends. It waits until
-// the first due time, at most scheduleCheck at a time, and also wakes at a set
-// or a remove. So a schedule that came due while the daemon was stopped runs at
-// the start. It returns the error of a write of the log or of the file.
+// the first due time, at most scheduleClockWaitTimeout at a time, and also
+// wakes at a set or a remove. So a schedule that came due while the daemon was
+// stopped runs at the start. It returns the error of a write of the log or of
+// the file.
func (s *Schedules) Run(ctx context.Context, l *TurnLoop) error {
for {
err := s.deliver(ctx, l)
wait, found := s.Wait(s.clock())
if found {
- timer = time.NewTimer(min(wait, scheduleCheck))
+ timer = time.NewTimer(min(wait, scheduleClockWaitTimeout))
timeout = timer.C
}
synctest.Wait()
require.Zero(t, loop.memory.Len())
clock.Set(clock.Now().Add(5 * time.Minute))
- time.Sleep(scheduleCheck)
+ time.Sleep(scheduleClockWaitTimeout)
synctest.Wait()
require.Equal(t, 1, loop.memory.Len())
require.Contains(t, events.Last("work"), "Check the clock.")
)
const (
- // telegramPollSeconds is how long one getUpdates call waits for an
- // update.
- telegramPollSeconds = 50
+ // telegramUpdateWaitTimeout is how long one getUpdates call waits for
+ // an update.
+ telegramUpdateWaitTimeout = 50 * time.Second
- // telegramPollSlack is how much longer than telegramPollSeconds a
+ // telegramPollSlack is how much longer than telegramUpdateWaitTimeout a
// getUpdates call can take before the bot gives it up.
telegramPollSlack = 20 * time.Second
// telegramPollTimeout is how long one getUpdates call can take.
- telegramPollTimeout = telegramPollSeconds*time.Second + telegramPollSlack
-
- // telegramSendTimeout is how long a sendMessage, sendRichMessage, or
- // sendChatAction call can take. A connection that stops without a
- // close, such as after a network change, then fails the call, and the
- // sender tries again, instead of a wait until TCP gives up. The client
- // has no timeout of its own, because the long poll of getUpdates needs
- // a longer limit.
+ telegramPollTimeout = telegramUpdateWaitTimeout + telegramPollSlack
+
+ // telegramSendTimeout is how long a sendMessage or sendRichMessage call
+ // can take. A connection that stops without a close, such as after a
+ // network change, then fails the call, and the sender tries again,
+ // instead of a wait until TCP gives up. The client has no timeout of
+ // its own, because the long poll of getUpdates needs a longer limit.
telegramSendTimeout = 30 * time.Second
+ // telegramFileLookupTimeout is the longest wait for image file details.
+ telegramFileLookupTimeout = 30 * time.Second
+
+ // telegramImageDownloadTimeout is the longest wait for image bytes.
+ telegramImageDownloadTimeout = 30 * time.Second
+
+ // telegramTypingTimeout is the longest wait to show the typing action.
+ telegramTypingTimeout = 30 * time.Second
+
// telegramAnswerBytes is the most bytes of one answer of the Bot API.
telegramAnswerBytes = 16 << 20
)
http *http.Client
}
-// GetUpdates waits for the updates from offset on, up to telegramPollSeconds,
-// and returns them. Telegram forgets each update before offset. Only new
-// messages are asked for.
+// GetUpdates waits for the updates from offset on, up to
+// telegramUpdateWaitTimeout, and returns them. Telegram forgets each update
+// before offset. Only new messages are asked for.
func (b *TelegramAPI) GetUpdates(ctx context.Context, offset int64) ([]TelegramUpdate, error) {
var updates []TelegramUpdate
err := b.call(ctx, telegramPollTimeout, "getUpdates", map[string]any{
"offset": offset,
- "timeout": telegramPollSeconds,
+ "timeout": int(telegramUpdateWaitTimeout / time.Second),
"allowed_updates": []string{"message"},
}, &updates)
Size int64 `json:"file_size"`
}
- err = b.call(ctx, telegramSendTimeout, "getFile", map[string]any{"file_id": id}, &file)
+ err = b.call(ctx, telegramFileLookupTimeout, "getFile", map[string]any{"file_id": id}, &file)
if err != nil {
return nil, fmt.Errorf("get image file: %w", err)
}
return nil, errors.New("invalid image file path")
}
- ctx, cancel := context.WithTimeout(ctx, telegramSendTimeout)
+ ctx, cancel := context.WithTimeout(ctx, telegramImageDownloadTimeout)
defer cancel()
request, err := http.NewRequestWithContext(ctx, http.MethodGet, b.base+"/file/bot"+b.token+"/"+file.Path, nil)
response, err := b.http.Do(request)
if err != nil {
- return nil, b.networkError("image download failed", err, telegramSendTimeout)
+ return nil, b.networkError("image download failed", err, telegramImageDownloadTimeout)
}
defer func() {
closeErr := response.Body.Close()
if closeErr != nil {
- err = errors.Join(err, b.networkError("close image download", closeErr, telegramSendTimeout))
+ err = errors.Join(err, b.networkError("close image download", closeErr, telegramImageDownloadTimeout))
}
}()
data, err = io.ReadAll(io.LimitReader(response.Body, imageMaxBytes+1))
if err != nil {
- return nil, b.networkError("image cut off", err, telegramSendTimeout)
+ return nil, b.networkError("image cut off", err, telegramImageDownloadTimeout)
}
if len(data) > imageMaxBytes {
}
// SendTyping shows the typing action in chat for about five seconds. A call
-// that takes longer than telegramSendTimeout fails with a TelegramNetworkError.
+// that takes longer than telegramTypingTimeout fails with a
+// TelegramNetworkError.
func (b *TelegramAPI) SendTyping(ctx context.Context, chat int64) error {
- return b.call(ctx, telegramSendTimeout, "sendChatAction", map[string]any{
+ return b.call(ctx, telegramTypingTimeout, "sendChatAction", map[string]any{
"chat_id": chat,
"action": "typing",
}, nil)
require.Equal(t, "getUpdates", call.method)
require.Equal(t, map[string]any{
"offset": float64(7),
- "timeout": float64(telegramPollSeconds),
+ "timeout": float64(telegramUpdateWaitTimeout / time.Second),
"allowed_updates": []any{"message"},
}, call.params)
}
// output, enough for toolClipCharacters/2 characters of 4 bytes.
toolClipBytes = 4 * toolClipCharacters / 2
- // toolShellWaitDelay is how long a command can keep its output open
+ // toolShellOutputTimeout is how long a command can keep its output open
// after it ends or after its kill.
- toolShellWaitDelay = 2 * time.Second
+ toolShellOutputTimeout = 2 * time.Second
)
// ToolLog is the log that the tools write their calls and results to.
cmd.Cancel = func() error {
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
- cmd.WaitDelay = toolShellWaitDelay
+ cmd.WaitDelay = toolShellOutputTimeout
output := &ToolHeadTail{}
cmd.Stdout = output
// whatsappTimeLayout is the layout of the times in the answers.
whatsappTimeLayout = "2006-01-02 15:04"
- // whatsappPairTime is the longest time of a pairing. The library closes
- // the pairing connection after about 160 seconds.
- whatsappPairTime = 3 * time.Minute
+ // whatsappPairTimeout is the longest time of a pairing. The library
+ // closes the pairing connection after about 160 seconds.
+ whatsappPairTimeout = 3 * time.Minute
- // whatsappLogoutTime is the longest time of the logout call of an
+ // whatsappLogoutTimeout is the longest time of the logout call of an
// unlink.
- whatsappLogoutTime = 30 * time.Second
+ whatsappLogoutTimeout = 30 * time.Second
- // whatsappNoticeDelay is how long a disconnection lasts before the user
- // gets a notice.
- whatsappNoticeDelay = 10 * time.Minute
+ // whatsappOutageNoticeDelay is the delay from a disconnection to its
+ // notice to the user.
+ whatsappOutageNoticeDelay = 10 * time.Minute
// whatsappRetryDelay is the first wait after a failed starting
// connection.
case w.isLinked():
request.answer("WhatsApp is linked already. Run /whatsapp unlink first.")
default:
- pairCtx, cancelPair := context.WithTimeout(ctx, whatsappPairTime)
+ pairCtx, cancelPair := context.WithTimeout(ctx, whatsappPairTimeout)
cancel = cancelPair
pairing.Go(func() {
defer cancelPair()
w.disconnectedAt = w.clock()
w.disconnections++
disconnections := w.disconnections
- w.timer = time.AfterFunc(whatsappNoticeDelay, func() {
+ w.timer = time.AfterFunc(whatsappOutageNoticeDelay, func() {
w.disconnectedLong(disconnections)
})
}
var logoutErr error
if client != nil {
- ctx, cancel := context.WithTimeout(ctx, whatsappLogoutTime)
+ ctx, cancel := context.WithTimeout(ctx, whatsappLogoutTimeout)
defer cancel()
logoutErr = client.Logout(ctx)
// handle takes one event of the library and tells if it was handled. A message
// goes to the inbox. The names of the groups follow the events of the groups. A
// connection starts a catch-up of the inbox, which ends at OfflineSyncCompleted
-// or after whatsappCatchupLimit. A disconnection keeps the time of its start
-// while Run or the library tries again. After whatsappNoticeDelay of a
+// or after whatsappCatchupTimeout. A disconnection keeps the time of its start
+// while Run or the library tries again. After whatsappOutageNoticeDelay of a
// disconnection, all clients get one notice, and they get one more when the
// connection comes back. A disconnection that the library does not try again
// gives a notice at once.
)
const (
- // whatsappGroupsLookupTime is the longest time of a read of group
+ // whatsappGroupLookupTimeout is the longest time of a read of group
// names.
- whatsappGroupsLookupTime = 10 * time.Second
+ whatsappGroupLookupTimeout = 10 * time.Second
// whatsappGroupsRefresh is the shortest time between two reads of the
// groups from WhatsApp for a group with no name.
return false
}
- ctx, cancel := context.WithTimeout(ctx, whatsappGroupsLookupTime)
+ ctx, cancel := context.WithTimeout(ctx, whatsappGroupLookupTimeout)
defer cancel()
groups, err := link.client.GetJoinedGroups(ctx)
synctest.Test(t, func(t *testing.T) {
whatsapp, fake := whatsappStartTestLinked(t)
whatsapp.client = fake
- fake.downloadWait = whatsappLookupTime + time.Second
+ fake.downloadWait = whatsappContactLookupTimeout + time.Second
data := imageTestPNG(t, 3, 2)
fake.downloadData = data
)
const (
- // whatsappLookupTime is the longest time of a read of names in the
- // session.
- whatsappLookupTime = 10 * time.Second
+ // whatsappContactLookupTimeout is the longest time of a read of names
+ // in the session.
+ whatsappContactLookupTimeout = 10 * time.Second
- // whatsappImageTime is the longest time of an image download.
- whatsappImageTime = 2 * time.Minute
+ // whatsappImageDownloadTimeout is the longest time of an image
+ // download.
+ whatsappImageDownloadTimeout = 2 * time.Minute
// whatsappSource is the source of the work events of the batches.
whatsappSource = "whatsapp"
- // whatsappCatchupLimit is the longest wait for the end of the catch-up
- // after a connection.
- whatsappCatchupLimit = time.Minute
+ // whatsappCatchupTimeout is the longest wait for the end of the
+ // catch-up after a connection.
+ whatsappCatchupTimeout = time.Minute
)
// newWhatsAppInbox returns the inbox of the batches in state, with clock for
//
// After each connection, the servers send the messages that came while the
// daemon was away, and then OfflineSyncCompleted. Until then, or until
-// whatsappCatchupLimit passes, the closed batches wait in the state file. At
+// whatsappCatchupTimeout passes, the closed batches wait in the state file. At
// the end of the catch-up, deliver closes all batches and logs them. It wakes
// the root once for its batches, so their catch-up gives one turn, not one for
// each chat. The batches of the state file at the start wait for the first
}
// startCatchup starts the catch-up of a new connection, which ends at
-// endCatchup or after whatsappCatchupLimit.
+// endCatchup or after whatsappCatchupTimeout.
func (i *WhatsAppInbox) startCatchup() {
i.mutex.Lock()
defer i.mutex.Unlock()
i.catchupTimer.Stop()
}
- i.catchupTimer = time.AfterFunc(whatsappCatchupLimit, func() {
+ i.catchupTimer = time.AfterFunc(whatsappCatchupTimeout, func() {
i.caughtUpBy(catchups)
})
}
// daemon. An image that cannot be downloaded or stored gets a text marker. A
// message that the batches do not take is handled with nothing to do.
func (i *WhatsAppInbox) receive(ctx context.Context, link WhatsAppLink, event *events.Message) bool {
- lookupCtx, cancel := context.WithTimeout(ctx, whatsappLookupTime)
+ lookupCtx, cancel := context.WithTimeout(ctx, whatsappContactLookupTimeout)
defer cancel()
chat, message, found := whatsappMessage(lookupCtx, link.device, link.own, event)
}
if event.Message.GetImageMessage() != nil {
- ctx, cancel := context.WithTimeout(ctx, whatsappImageTime)
+ ctx, cancel := context.WithTimeout(ctx, whatsappImageDownloadTimeout)
defer cancel()
message.Text = i.imageText(ctx, link, event, message.Text)
synctest.Wait()
require.True(t, test.fake.Emit(whatsappTestMessage(whatsappTestAna, whatsappTestOwn, "m1", "On my way.", time.Now())))
synctest.Wait()
- time.Sleep(whatsappBatchQuiet - time.Nanosecond)
+ time.Sleep(whatsappBatchIdleTimeout - time.Nanosecond)
synctest.Wait()
require.Zero(t, test.loop.memory.Len())
require.NoError(t, test.whatsapp.inbox.deliver(t.Context(), test.loop, test.whatsapp.link(), false))
require.Zero(t, test.loop.memory.Len())
- time.Sleep(whatsappCatchupLimit)
+ time.Sleep(whatsappCatchupTimeout)
synctest.Wait()
require.NoError(t, test.whatsapp.inbox.deliver(t.Context(), test.loop, test.whatsapp.link(), false))
require.Equal(t, 1, test.loop.memory.Len())
)
const (
- // whatsappSendTime is the longest time of a send.
- whatsappSendTime = 30 * time.Second
+ // whatsappSendTimeout is the longest time of a send.
+ whatsappSendTimeout = 30 * time.Second
// whatsappSendBytes is the most bytes of the text of a send.
whatsappSendBytes = 65536
return "", err
}
- sendCtx, cancel := context.WithTimeout(ctx, whatsappSendTime)
+ sendCtx, cancel := context.WithTimeout(ctx, whatsappSendTimeout)
defer cancel()
response, err := link.client.SendMessage(sendCtx, jid, &waE2E.Message{
// the batches that are not in the log yet and the records of the sends.
whatsappStateFileName = "state.json"
- // whatsappBatchQuiet is how long a chat is quiet before its batch
+ // whatsappBatchIdleTimeout is how long a chat is quiet before its batch
// closes.
- whatsappBatchQuiet = 20 * time.Second
+ whatsappBatchIdleTimeout = 20 * time.Second
- // whatsappBatchWindow is the longest time of a batch.
- whatsappBatchWindow = 2 * time.Minute
+ // whatsappBatchTimeout is the longest time of a batch.
+ whatsappBatchTimeout = 2 * time.Minute
// whatsappBatchMessages is the most messages of a batch.
whatsappBatchMessages = 50
)
// openWhatsAppState reads the batches and the send records of the state file at
-// path. A missing file holds none. A batch closes after whatsappBatchQuiet with
-// no message, or after whatsappBatchWindow at the latest.
+// path. A missing file holds none. A batch closes after
+// whatsappBatchIdleTimeout with no message, or after whatsappBatchTimeout at
+// the latest.
func openWhatsAppState(path string) (*WhatsAppState, error) {
s := &WhatsAppState{
path: path,
// WhatsAppState holds the state file: the send records, and the batches of the
// WhatsApp messages that are not in the log yet, open and closed, in the order
// of their first message. The messages of one chat form one batch while the
-// times of the messages are at most whatsappBatchQuiet apart, at most
-// whatsappBatchWindow after the first, and the batch has fewer than
+// times of the messages are at most whatsappBatchIdleTimeout apart, at most
+// whatsappBatchTimeout after the first, and the batch has fewer than
// whatsappBatchMessages messages. The times are the WhatsApp times, so a
// catch-up after a stop gives the same batches as live messages. A batch also
-// closes when whatsappBatchQuiet passes after the arrival of its last message,
-// or whatsappBatchWindow after the arrival of its first message, in real time.
+// closes when whatsappBatchIdleTimeout passes after the arrival of its last
+// message, or whatsappBatchTimeout after the arrival of its first message, in
+// real time.
//
// A send record keeps the result of a send by its ID, so that a repeated call
// of the send tool does not send again. The state file keeps the last
return false, nil
}
- if message.Time.Sub(batch.Messages[len(batch.Messages)-1].Time) > whatsappBatchQuiet || message.Time.Sub(batch.Messages[0].Time) > whatsappBatchWindow {
+ if message.Time.Sub(batch.Messages[len(batch.Messages)-1].Time) > whatsappBatchIdleTimeout || message.Time.Sub(batch.Messages[0].Time) > whatsappBatchTimeout {
batch.Closed = true
closed = true
index = -1
// deadline returns when batch closes in real time.
func (s *WhatsAppState) deadline(batch WhatsAppBatch) time.Time {
- quiet := batch.Last.Add(whatsappBatchQuiet)
- window := batch.First.Add(whatsappBatchWindow)
+ quiet := batch.Last.Add(whatsappBatchIdleTimeout)
+ window := batch.First.Add(whatsappBatchTimeout)
if window.Before(quiet) {
return window
}
since := time.Now().Format(whatsappTimeLayout)
- time.Sleep(whatsappNoticeDelay)
+ time.Sleep(whatsappOutageNoticeDelay)
synctest.Wait()
require.Equal(t, 1, test.events.Count("notice"))
require.Equal(t, "WhatsApp is disconnected since "+since+". OneChat tries again.", test.events.Last("notice"))
- time.Sleep(whatsappNoticeDelay)
+ time.Sleep(whatsappOutageNoticeDelay)
synctest.Wait()
require.Equal(t, 1, test.events.Count("notice"))
test.fake.mutex.Lock()
require.True(t, test.fake.Emit(&events.Connected{}))
require.True(t, test.fake.Emit(&events.Disconnected{}))
require.True(t, test.fake.Emit(&events.Connected{}))
- time.Sleep(whatsappNoticeDelay + time.Second)
+ time.Sleep(whatsappOutageNoticeDelay + time.Second)
synctest.Wait()
require.Zero(t, test.events.Count("notice"))
require.True(t, test.fake.Emit(&events.Disconnected{}))
require.True(t, test.fake.Emit(&events.Disconnected{}))
- time.Sleep(whatsappNoticeDelay)
+ time.Sleep(whatsappOutageNoticeDelay)
synctest.Wait()
require.Equal(t, 1, test.events.Count("notice"))
require.Contains(t, test.events.Last("notice"), "OneChat tries again.")
require.True(t, test.fake.Emit(&events.Disconnected{}))
- time.Sleep(whatsappNoticeDelay)
+ time.Sleep(whatsappOutageNoticeDelay)
synctest.Wait()
require.Equal(t, 1, test.events.Count("notice"))