aboutsummaryrefslogtreecommitdiffstats
path: root/configserver/src/main/java/com/yahoo/vespa/config/server/TimeoutBudget.java
blob: 24eb18ff7ea52b40dc43ddc81e3d3d4cce5d128a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.config.server;

import com.yahoo.concurrent.UncheckedTimeoutException;

import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

/**
 * Handles a timeout logic by providing higher level abstraction for asking if there is time left.
 *
 * @author Ulf Lilleengen
 */
public class TimeoutBudget {

    private final Clock clock;
    private final Instant startTime;
    private final List<Measurement> measurements = new ArrayList<>();
    private final Instant endTime;

    public TimeoutBudget(Clock clock, Duration duration) {
        this.clock = clock;
        this.startTime = clock.instant();
        this.endTime = startTime.plus(duration);
    }

    public Duration timeout() {
        return Duration.between(startTime, endTime);
    }

    public Duration timeLeft() {
        Instant now = clock.instant();
        Duration duration = Duration.between(now, endTime);
        return duration.isNegative() ? Duration.ZERO : duration;
    }

    public boolean hasTimeLeft() {
        return clock.instant().isBefore(endTime);
    }

    public boolean hasTimeLeft(String step) {
        Instant now = clock.instant();
        measurements.add(new Measurement(now, step));
        return now.isBefore(endTime);
    }

    public String timesUsed() {
        StringBuilder buf = new StringBuilder();
        buf.append("[");
        Instant prev = startTime;
        for (Measurement m : measurements) {
            if ( ! m.label().isEmpty()) {
                buf.append(m.label()).append(": ");
            }
            buf.append(Duration.between(prev, m.timestamp()).toMillis())
                    .append(" ms")
                    .append(", ");
            prev = m.timestamp();
        }
        Instant now = clock.instant();
        buf.append("total: ");
        buf.append(Duration.between(startTime, now).toMillis());
        buf.append(" ms]");
        return buf.toString();
    }

    /**
     * @param exceptionMessage exception message for the exception that will be thrown if there is no time left
     * @throws UncheckedTimeoutException if this has no time left
     */
    public void assertNotTimedOut(Supplier<String> exceptionMessage) {
        if (hasTimeLeft()) return;
        throw new UncheckedTimeoutException(exceptionMessage.get());
    }

    private static class Measurement {

        private final Instant timestamp;
        private final String label;

        Measurement(Instant timestamp, String label) {
            this.timestamp = timestamp;
            this.label = label;
        }

        public Instant timestamp() {
            return timestamp;
        }

        public String label() {
            return label;
        }
    }
}