Fix waiting for connections.
[xudocci.git] / src / main / java / net / pterodactylus / xdcc / core / ConnectionBackoff.java
1 package net.pterodactylus.xdcc.core;
2
3 import java.time.Clock;
4 import java.time.Instant;
5 import java.util.HashMap;
6 import java.util.Map;
7 import java.util.concurrent.TimeUnit;
8
9 import net.pterodactylus.xdcc.data.Network;
10
11 /**
12  * Manages backoff times for connections.
13  *
14  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
15  */
16 public class ConnectionBackoff {
17
18         private static final long MILLIS_PER_SECOND = TimeUnit.MINUTES.toMillis(1);
19         private static final long MILLIS_PER_HOUR = TimeUnit.HOURS.toMillis(1);
20
21         private final Clock clock;
22         private final Map<Network, ConnectionFailureCounter> connectionFailures = new HashMap<>();
23
24         public ConnectionBackoff() {
25                 this(Clock.systemDefaultZone());
26         }
27
28         public ConnectionBackoff(Clock clock) {
29                 this.clock = clock;
30         }
31
32         public void connectionFailed(Network network) {
33                 ConnectionFailureCounter connectionFailureCounter = getConnectionFailureCounter(network);
34                 connectionFailureCounter.countFailure();
35         }
36
37         public void connectionSuccessful(Network network) {
38                 ConnectionFailureCounter connectionFailureCounter = getConnectionFailureCounter(network);
39                 connectionFailureCounter.reset();
40         }
41
42         public long getBackoff(Network network) {
43                 ConnectionFailureCounter connectionFailureCounter = getConnectionFailureCounter(network);
44                 if (!connectionFailureCounter.hasFailure()) {
45                         return 0;
46                 }
47                 long delay = (long) (MILLIS_PER_SECOND *
48                                 Math.pow(1.2, connectionFailureCounter.getFailureCount() - 1));
49                 return Math.min(MILLIS_PER_HOUR, Math.max(0,
50                                 (connectionFailureCounter.getLastFailureTime() + delay) - Instant.now(clock)
51                                                 .toEpochMilli()));
52         }
53
54         private ConnectionFailureCounter getConnectionFailureCounter(Network network) {
55                 ConnectionFailureCounter connectionFailureCounter;
56                 synchronized (connectionFailures) {
57                         if (!connectionFailures.containsKey(network)) {
58                                 connectionFailures.put(network, new ConnectionFailureCounter());
59                         }
60                         connectionFailureCounter = connectionFailures.get(network);
61                 }
62                 return connectionFailureCounter;
63         }
64
65         public long getConnectionTime(Network network) {
66                 return Instant.now(clock).toEpochMilli() + getBackoff(network);
67         }
68
69         private class ConnectionFailureCounter {
70
71                 private final Object lock = new Object();
72                 private int count;
73                 private long lastFailureTime;
74
75                 public void reset() {
76                         synchronized (lock) {
77                                 count = 0;
78                                 lastFailureTime = 0;
79                         }
80                 }
81
82                 public void countFailure() {
83                         synchronized (lock) {
84                                 count++;
85                                 lastFailureTime = Instant.now(clock).toEpochMilli();
86                         }
87                 }
88
89                 public boolean hasFailure() {
90                         synchronized (lock) {
91                                 return (count > 0);
92                         }
93                 }
94
95                 public int getFailureCount() {
96                         synchronized (lock) {
97                                 return count;
98                         }
99                 }
100
101                 public long getLastFailureTime() {
102                         synchronized (lock) {
103                                 return lastFailureTime;
104                         }
105                 }
106
107         }
108
109 }