aboutsummaryrefslogtreecommitdiffstats
path: root/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectionThrottler.java
blob: 58d1142c563db32a69472dc4a101f9c021caa787 (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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc.http.server.jetty;

import com.yahoo.jdisc.http.ConnectorConfig;
import org.eclipse.jetty.server.AcceptRateLimit;
import org.eclipse.jetty.server.ConnectionLimit;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.LowResourceMonitor;
import org.eclipse.jetty.util.component.LifeCycle;

import java.time.Duration;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.TimeUnit;

import static java.util.Collections.singleton;

/**
 * Throttles new connections using {@link LowResourceMonitor}, {@link AcceptRateLimit} and {@link ConnectionLimit}.
 *
 * @author bjorncs
 */
class ConnectionThrottler {

    private final Object monitor = new Object();
    private final Queue<Runnable> throttleResetters = new ArrayDeque<>();
    private final Collection<LifeCycle> beans = new ArrayList<>();
    private final Connector connector;
    private int throttlersCount;

    ConnectionThrottler(Connector connector, ConnectorConfig.Throttling config) {
        this.connector = connector;
        Duration idleTimeout = fromSeconds(config.idleTimeout());
        if (config.maxAcceptRate() != -1) {
            beans.add(new CoordinatedAcceptRateLimit(config.maxAcceptRate(), fromSeconds(config.maxAcceptRatePeriod())));
        }
        if (config.maxConnections() != -1) {
            beans.add(new CoordinatedConnectionLimit(config.maxConnections(), idleTimeout));
        }
        if (config.maxHeapUtilization() != -1) {
            beans.add(new CoordinatedLowResourcesLimit(config.maxHeapUtilization(), idleTimeout));
        }
    }

    void registerBeans() {
        beans.forEach(bean -> connector.getServer().addBean(bean));
    }

    private static Duration fromSeconds(double seconds) {
        return Duration.ofMillis((long) (seconds * 1000));
    }

    private void onThrottle(Runnable throttleResetter) {
        synchronized (monitor) {
            ++throttlersCount;
            throttleResetters.offer(throttleResetter);
        }
    }

    private void onReset() {
        List<Runnable> resetters = new ArrayList<>();
        synchronized (monitor) {
            if (--throttlersCount == 0) {
                resetters.addAll(throttleResetters);
                throttleResetters.clear();
            }
        }
        resetters.forEach(Runnable::run);
    }
    private static long toMaxMemoryUsageInBytes(double maxHeapUtilization) {
        return (long) (maxHeapUtilization * Runtime.getRuntime().maxMemory());
    }

    private class CoordinatedLowResourcesLimit extends LowResourceMonitor {

        CoordinatedLowResourcesLimit(double maxHeapUtilization, Duration idleTimeout) {
            super(connector.getServer());
            super.setMonitoredConnectors(singleton(connector));
            super.setMaxMemory(toMaxMemoryUsageInBytes(maxHeapUtilization));
            super.setLowResourcesIdleTimeout((int)idleTimeout.toMillis());
        }

        @Override
        protected void setLowResources() {
            super.setLowResources();
            ConnectionThrottler.this.onThrottle(() -> super.clearLowResources());
        }

        @Override
        protected void clearLowResources() {
            ConnectionThrottler.this.onReset();
        }
    }
    private class CoordinatedConnectionLimit extends ConnectionLimit {

        CoordinatedConnectionLimit(int maxConnections, Duration idleTimeout) {
            super(maxConnections, connector);
            super.setIdleTimeout(idleTimeout.toMillis());
        }

        @Override
        protected void limit() {
            super.limit();
            ConnectionThrottler.this.onThrottle(() -> super.unlimit());
        }

        @Override
        protected void unlimit() {
            ConnectionThrottler.this.onReset();
        }
    }

    private class CoordinatedAcceptRateLimit extends AcceptRateLimit {
        CoordinatedAcceptRateLimit(int limit, Duration period) {
            super(limit, period.toMillis(), TimeUnit.MILLISECONDS, connector);
        }

        @Override
        protected void limit() {
            super.limit();
            ConnectionThrottler.this.onThrottle(() -> super.unlimit());
        }

        @Override
        protected void unlimit() {
            ConnectionThrottler.this.onReset();
        }
    }
}