aboutsummaryrefslogtreecommitdiffstats
path: root/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectionThrottler.java
blob: 370ac0aa788271ddd9e530901220de58332f739b (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
// 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.maxMemoryUsage() != -1) {
            beans.add(new CoordinatedLowResourcesLimit(config.maxMemoryUsage(), idleTimeout));
        }
    }

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

    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 class CoordinatedLowResourcesLimit extends LowResourceMonitor {
        CoordinatedLowResourcesLimit(int maxMemoryUsageMegaBytes, Duration idleTimeout) {
            super(connector.getServer());
            super.setMonitoredConnectors(singleton(connector));
            super.setMaxMemory(maxMemoryUsageMegaBytes * 1024 * 1024L);
            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();
        }
    }
}