17
17
import java .lang .foreign .MemorySegment ;
18
18
import java .lang .foreign .ValueLayout ;
19
19
import java .nio .charset .StandardCharsets ;
20
- import java .util .ArrayList ;
21
20
import java .util .Arrays ;
22
21
import java .util .List ;
22
+ import java .util .Set ;
23
+ import java .util .concurrent .CompletableFuture ;
24
+ import java .util .concurrent .CopyOnWriteArrayList ;
23
25
import java .util .concurrent .ExecutorService ;
24
26
import java .util .concurrent .RejectedExecutionException ;
27
+ import java .util .concurrent .atomic .AtomicBoolean ;
28
+ import java .util .concurrent .atomic .AtomicInteger ;
25
29
26
- import static ru .vk .itmo .test .alenkovayulya .ShardRouter .REDIRECT_HEADER ;
27
- import static ru .vk .itmo .test .alenkovayulya .ShardRouter .TIMESTAMP_HEADER ;
28
- import static ru .vk .itmo .test .alenkovayulya .ShardRouter .redirectRequest ;
30
+ import static ru .vk .itmo .test .alenkovayulya .ShardRouter .*;
29
31
30
32
public class ServerImpl extends HttpServer {
31
33
@@ -34,6 +36,11 @@ public class ServerImpl extends HttpServer {
34
36
private final ExecutorService executorService ;
35
37
private final String url ;
36
38
private final ShardSelector shardSelector ;
39
+ private static final Set <Integer > ALLOWED_METHODS = Set .of (
40
+ Request .METHOD_GET ,
41
+ Request .METHOD_PUT ,
42
+ Request .METHOD_DELETE
43
+ );
37
44
private static final int [] AVAILABLE_GOOD_RESPONSE_CODES = new int [] {200 , 201 , 202 , 404 };
38
45
39
46
public ServerImpl (ServiceConfig serviceConfig ,
@@ -59,6 +66,11 @@ private static HttpServerConfig createServerConfig(ServiceConfig serviceConfig)
59
66
60
67
@ Override
61
68
public void handleRequest (Request request , HttpSession session ) throws IOException {
69
+ if (!ALLOWED_METHODS .contains (request .getMethod ())) {
70
+ sendEmptyResponse (Response .METHOD_NOT_ALLOWED , session );
71
+ return ;
72
+ }
73
+
62
74
String id = request .getParameter ("id=" );
63
75
if (isEmptyId (id )) {
64
76
sendEmptyResponse (Response .BAD_REQUEST , session );
@@ -104,62 +116,107 @@ private void handleAsLeader(Request request, HttpSession session, String id) {
104
116
LOGGER .error ("Request rejected by policy" , e );
105
117
sendEmptyResponse (Response .SERVICE_UNAVAILABLE , session );
106
118
}
107
-
108
119
}
109
120
110
121
private void collectResponses (Request request ,
111
122
HttpSession session ,
112
123
String id ,
113
124
int from ,
114
125
int ack
115
- ) throws IOException {
116
- List <Response > responses = new ArrayList <>();
126
+ ) {
127
+ List <CompletableFuture < Response >> asyncResponses = new CopyOnWriteArrayList <>();
117
128
long timestamp = System .currentTimeMillis ();
118
129
int firstOwnerShardIndex = shardSelector .getOwnerShardIndex (id );
119
130
120
131
for (int i = 0 ; i < from ; i ++) {
132
+ CompletableFuture <Response > asyncResponse ;
121
133
int shardIndex = (firstOwnerShardIndex + i ) % shardSelector .getClusterSize ();
122
134
123
135
if (isRedirectNeeded (shardIndex )) {
124
- handleRedirect (request , timestamp , shardIndex , responses );
136
+ asyncResponse = handleRedirect (request , timestamp , shardIndex );
125
137
} else {
126
- Response response = handleInternalRequest (request , id , timestamp );
127
- responses .add (response );
138
+ asyncResponse = handleInternalRequestAsync (request , id , timestamp );
128
139
}
129
140
141
+ asyncResponses .add (asyncResponse );
142
+
130
143
}
131
144
132
- checkReplicasResponsesNumber (request , session , responses , ack );
145
+ handleAsyncResponses (session , ack , from , request , asyncResponses );
146
+
147
+ }
148
+
149
+ private void handleAsyncResponses (
150
+ HttpSession session , int ack , int from , Request request ,
151
+ List <CompletableFuture <Response >> completableFutureResponses
152
+ ) {
153
+ List <Response > validResponses = new CopyOnWriteArrayList <>();
154
+ AtomicBoolean isEnoughValidResponses = new AtomicBoolean ();
155
+ AtomicInteger allResponsesCounter = new AtomicInteger ();
156
+
157
+ for (CompletableFuture <Response > completableFuture : completableFutureResponses ) {
158
+ completableFuture .whenCompleteAsync ((response , throwable ) -> {
159
+ if (isEnoughValidResponses .get ()) {
160
+ return ;
161
+ }
162
+ allResponsesCounter .incrementAndGet ();
163
+
164
+ if (throwable != null ) {
165
+ response = new Response (Response .INTERNAL_ERROR );
166
+ }
167
+
168
+ if (isValidResponse (response )) {
169
+ validResponses .add (response );
170
+ }
171
+
172
+ sendResponseIfEnoughReplicasResponsesNumber (request , isEnoughValidResponses , session , validResponses , ack );
173
+
174
+ if (allResponsesCounter .get () == from && validResponses .size () < ack ) {
175
+ sendEmptyResponse ("504 Not Enough Replicas" , session );
176
+ }
177
+ }, executorService ).exceptionally ((th ) -> new Response (Response .INTERNAL_ERROR ));
178
+ }
133
179
}
134
180
135
- private void checkReplicasResponsesNumber (
181
+
182
+ private void sendResponseIfEnoughReplicasResponsesNumber (
136
183
Request request ,
184
+ AtomicBoolean isEnoughValidResponses ,
137
185
HttpSession session ,
138
186
List <Response > responses ,
139
187
int ack
140
- ) throws IOException {
141
- if (responses .size () >= ack ) {
142
- if (request .getMethod () == Request .METHOD_GET ) {
143
- session .sendResponse (getResponseWithMaxTimestamp (responses ));
144
- } else {
145
- session .sendResponse (responses .getFirst ());
188
+ ) {
189
+ try {
190
+ if (responses .size () >= ack ) {
191
+ isEnoughValidResponses .set (true );
192
+ if (request .getMethod () == Request .METHOD_GET ) {
193
+ session .sendResponse (getResponseWithMaxTimestamp (responses ));
194
+ } else {
195
+ session .sendResponse (responses .getFirst ());
196
+ }
146
197
}
147
- } else {
148
- sendEmptyResponse ("504 Not Enough Replicas" , session );
198
+ } catch (IOException e ) {
199
+ LOGGER .error ("Exception during send win response: " , e );
200
+ sendEmptyResponse (Response .INTERNAL_ERROR , session );
201
+ session .close ();
149
202
}
150
203
}
151
204
152
- private void handleRedirect (Request request , long timestamp , int nodeIndex , List <Response > responses ) {
153
- Response response = redirectRequest (request .getMethodName (),
205
+ private boolean isValidResponse (Response response ){
206
+ return Arrays .stream (AVAILABLE_GOOD_RESPONSE_CODES )
207
+ .anyMatch (code -> code == response .getStatus ());
208
+ }
209
+
210
+ private CompletableFuture <Response > handleRedirect (Request request , long timestamp , int nodeIndex ) {
211
+ return redirectRequest (request .getMethodName (),
154
212
request .getParameter ("id=" ),
155
213
shardSelector .getShardUrlByIndex (nodeIndex ),
156
214
request .getBody () == null
157
215
? new byte [0 ] : request .getBody (), timestamp );
158
- boolean correctRes = Arrays .stream (AVAILABLE_GOOD_RESPONSE_CODES )
159
- .anyMatch (code -> code == response .getStatus ());
160
- if (correctRes ) {
161
- responses .add (response );
162
- }
216
+ }
217
+
218
+ private CompletableFuture <Response > handleInternalRequestAsync (Request request , String id , long timestamp ) {
219
+ return CompletableFuture .supplyAsync (() -> handleInternalRequest (request , id , timestamp ), ShardRouter .proxyExecutor );
163
220
}
164
221
165
222
private Response handleInternalRequest (Request request , String id , long timestamp ) {
@@ -233,7 +290,6 @@ private void sendEmptyResponse(String response, HttpSession session) {
233
290
session .sendResponse (emptyRes );
234
291
} catch (IOException e ) {
235
292
LOGGER .info ("Exception during sending the empty response: " , e );
236
- session .close ();
237
293
}
238
294
}
239
295
0 commit comments