1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.ipc;
19
20 import java.io.IOException;
21 import java.net.ConnectException;
22 import java.net.InetSocketAddress;
23 import java.net.SocketException;
24 import java.nio.ByteBuffer;
25 import java.security.PrivilegedExceptionAction;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Random;
32 import java.util.concurrent.TimeUnit;
33
34 import javax.security.sasl.SaslException;
35
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38 import org.apache.hadoop.hbase.HConstants;
39 import org.apache.hadoop.hbase.classification.InterfaceAudience;
40 import org.apache.hadoop.hbase.client.MetricsConnection;
41 import org.apache.hadoop.hbase.exceptions.ConnectionClosingException;
42 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
43 import org.apache.hadoop.hbase.protobuf.generated.AuthenticationProtos;
44 import org.apache.hadoop.hbase.protobuf.generated.AuthenticationProtos.TokenIdentifier.Kind;
45 import org.apache.hadoop.hbase.protobuf.generated.RPCProtos;
46 import org.apache.hadoop.hbase.protobuf.generated.TracingProtos;
47 import org.apache.hadoop.hbase.security.AuthMethod;
48 import org.apache.hadoop.hbase.security.SaslClientHandler;
49 import org.apache.hadoop.hbase.security.SaslUtil;
50 import org.apache.hadoop.hbase.security.SecurityInfo;
51 import org.apache.hadoop.hbase.security.User;
52 import org.apache.hadoop.hbase.security.token.AuthenticationTokenSelector;
53 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
54 import org.apache.hadoop.io.Text;
55 import org.apache.hadoop.ipc.RemoteException;
56 import org.apache.hadoop.security.SecurityUtil;
57 import org.apache.hadoop.security.UserGroupInformation;
58 import org.apache.hadoop.security.token.Token;
59 import org.apache.hadoop.security.token.TokenIdentifier;
60 import org.apache.hadoop.security.token.TokenSelector;
61 import org.apache.htrace.Span;
62 import org.apache.htrace.Trace;
63
64 import com.google.protobuf.Descriptors;
65 import com.google.protobuf.Message;
66 import com.google.protobuf.RpcCallback;
67
68 import io.netty.bootstrap.Bootstrap;
69 import io.netty.buffer.ByteBuf;
70 import io.netty.buffer.ByteBufOutputStream;
71 import io.netty.channel.Channel;
72 import io.netty.channel.ChannelFuture;
73 import io.netty.channel.ChannelFutureListener;
74 import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
75 import io.netty.util.Timeout;
76 import io.netty.util.TimerTask;
77 import io.netty.util.concurrent.GenericFutureListener;
78 import io.netty.util.concurrent.Promise;
79
80
81
82
83 @InterfaceAudience.Private
84 public class AsyncRpcChannel {
85 private static final Log LOG = LogFactory.getLog(AsyncRpcChannel.class.getName());
86
87 private static final int MAX_SASL_RETRIES = 5;
88
89 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="MS_MUTABLE_COLLECTION_PKGPROTECT",
90 justification="the rest of the system which live in the different package can use")
91 protected final static Map<Kind, TokenSelector<? extends TokenIdentifier>> TOKEN_HANDDLERS
92 = new HashMap<>();
93
94 static {
95 TOKEN_HANDDLERS.put(AuthenticationProtos.TokenIdentifier.Kind.HBASE_AUTH_TOKEN,
96 new AuthenticationTokenSelector());
97 }
98
99 final AsyncRpcClient client;
100
101
102
103 private Channel channel;
104
105 String name;
106 final User ticket;
107 final String serviceName;
108 final InetSocketAddress address;
109
110 private int ioFailureCounter = 0;
111 private int connectFailureCounter = 0;
112
113 boolean useSasl;
114 AuthMethod authMethod;
115 private int reloginMaxBackoff;
116 private Token<? extends TokenIdentifier> token;
117 private String serverPrincipal;
118
119
120 private final Map<Integer, AsyncCall> pendingCalls = new HashMap<Integer, AsyncCall>();
121 private boolean connected = false;
122 private boolean closed = false;
123
124 private Timeout cleanupTimer;
125
126 private final TimerTask timeoutTask = new TimerTask() {
127 @Override
128 public void run(Timeout timeout) throws Exception {
129 cleanupCalls();
130 }
131 };
132
133
134
135
136
137
138
139
140
141 public AsyncRpcChannel(Bootstrap bootstrap, final AsyncRpcClient client, User ticket,
142 String serviceName, InetSocketAddress address) {
143 this.client = client;
144
145 this.ticket = ticket;
146 this.serviceName = serviceName;
147 this.address = address;
148
149 this.channel = connect(bootstrap).channel();
150
151 name = ("IPC Client (" + channel.hashCode() + ") to " + address.toString()
152 + ((ticket == null) ? " from unknown user" : (" from " + ticket.getName())));
153 }
154
155
156
157
158
159
160 private ChannelFuture connect(final Bootstrap bootstrap) {
161 return bootstrap.remoteAddress(address).connect()
162 .addListener(new GenericFutureListener<ChannelFuture>() {
163 @Override
164 public void operationComplete(final ChannelFuture f) throws Exception {
165 if (!f.isSuccess()) {
166 if (f.cause() instanceof SocketException) {
167 retryOrClose(bootstrap, connectFailureCounter++, f.cause());
168 } else {
169 retryOrClose(bootstrap, ioFailureCounter++, f.cause());
170 }
171 return;
172 }
173 channel = f.channel();
174
175 setupAuthorization();
176
177 ByteBuf b = channel.alloc().directBuffer(6);
178 createPreamble(b, authMethod);
179 channel.writeAndFlush(b).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
180 if (useSasl) {
181 UserGroupInformation ticket = AsyncRpcChannel.this.ticket.getUGI();
182 if (authMethod == AuthMethod.KERBEROS) {
183 if (ticket != null && ticket.getRealUser() != null) {
184 ticket = ticket.getRealUser();
185 }
186 }
187 SaslClientHandler saslHandler;
188 if (ticket == null) {
189 throw new FatalConnectionException("ticket/user is null");
190 }
191 final UserGroupInformation realTicket = ticket;
192 saslHandler = ticket.doAs(new PrivilegedExceptionAction<SaslClientHandler>() {
193 @Override
194 public SaslClientHandler run() throws IOException {
195 return getSaslHandler(realTicket, bootstrap);
196 }
197 });
198 if (saslHandler != null) {
199
200 channel.pipeline().addFirst(saslHandler);
201 } else {
202
203 authMethod = AuthMethod.SIMPLE;
204 useSasl = false;
205 }
206 } else {
207 startHBaseConnection(f.channel());
208 }
209 }
210 });
211 }
212
213
214
215
216
217 private void startHBaseConnection(Channel ch) {
218 ch.pipeline().addLast("frameDecoder",
219 new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
220 ch.pipeline().addLast(new AsyncServerResponseHandler(this));
221 try {
222 writeChannelHeader(ch).addListener(new GenericFutureListener<ChannelFuture>() {
223 @Override
224 public void operationComplete(ChannelFuture future) throws Exception {
225 if (!future.isSuccess()) {
226 close(future.cause());
227 return;
228 }
229 List<AsyncCall> callsToWrite;
230 synchronized (pendingCalls) {
231 connected = true;
232 callsToWrite = new ArrayList<AsyncCall>(pendingCalls.values());
233 }
234 for (AsyncCall call : callsToWrite) {
235 writeRequest(call);
236 }
237 }
238 });
239 } catch (IOException e) {
240 close(e);
241 }
242 }
243
244
245
246
247
248 private void startConnectionWithEncryption(Channel ch) {
249
250
251
252
253 ch.pipeline().addFirst("beforeUnwrapDecoder",
254 new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 0));
255 ch.pipeline().addLast("afterUnwrapDecoder",
256 new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
257 ch.pipeline().addLast(new AsyncServerResponseHandler(this));
258 List<AsyncCall> callsToWrite;
259 synchronized (pendingCalls) {
260 connected = true;
261 callsToWrite = new ArrayList<AsyncCall>(pendingCalls.values());
262 }
263 for (AsyncCall call : callsToWrite) {
264 writeRequest(call);
265 }
266 }
267
268
269
270
271
272
273
274 private SaslClientHandler getSaslHandler(final UserGroupInformation realTicket,
275 final Bootstrap bootstrap) throws IOException {
276 return new SaslClientHandler(realTicket, authMethod, token, serverPrincipal,
277 client.fallbackAllowed,
278 client.conf.get("hbase.rpc.protection",
279 SaslUtil.QualityOfProtection.AUTHENTICATION.name().toLowerCase()),
280 getChannelHeaderBytes(authMethod),
281 new SaslClientHandler.SaslExceptionHandler() {
282 @Override
283 public void handle(int retryCount, Random random, Throwable cause) {
284 try {
285
286 handleSaslConnectionFailure(retryCount, cause, realTicket);
287
288
289 client.newTimeout(new TimerTask() {
290 @Override
291 public void run(Timeout timeout) throws Exception {
292 connect(bootstrap);
293 }
294 }, random.nextInt(reloginMaxBackoff) + 1, TimeUnit.MILLISECONDS);
295 } catch (IOException | InterruptedException e) {
296 close(e);
297 }
298 }
299 }, new SaslClientHandler.SaslSuccessfulConnectHandler() {
300 @Override
301 public void onSuccess(Channel channel) {
302 startHBaseConnection(channel);
303 }
304
305 @Override
306 public void onSaslProtectionSucess(Channel channel) {
307 startConnectionWithEncryption(channel);
308 }
309 });
310 }
311
312
313
314
315
316
317
318
319 private void retryOrClose(final Bootstrap bootstrap, int connectCounter, Throwable e) {
320 if (connectCounter < client.maxRetries) {
321 client.newTimeout(new TimerTask() {
322 @Override public void run(Timeout timeout) throws Exception {
323 connect(bootstrap);
324 }
325 }, client.failureSleep, TimeUnit.MILLISECONDS);
326 } else {
327 client.failedServers.addToFailedServers(address);
328 close(e);
329 }
330 }
331
332
333
334
335
336
337
338
339 public Promise<Message> callMethod(final Descriptors.MethodDescriptor method,
340 final PayloadCarryingRpcController controller, final Message request,
341 final Message responsePrototype, MetricsConnection.CallStats callStats) {
342 final AsyncCall call = new AsyncCall(channel.eventLoop(), client.callIdCnt.getAndIncrement(),
343 method, request, controller, responsePrototype, callStats);
344 controller.notifyOnCancel(new RpcCallback<Object>() {
345 @Override
346 public void run(Object parameter) {
347
348 synchronized (pendingCalls) {
349 pendingCalls.remove(call.id);
350 }
351 }
352 });
353
354 if (controller.isCanceled()) {
355
356 call.cancel(true);
357 return call;
358 }
359
360 synchronized (pendingCalls) {
361 if (closed) {
362 Promise<Message> promise = channel.eventLoop().newPromise();
363 promise.setFailure(new ConnectException());
364 return promise;
365 }
366 pendingCalls.put(call.id, call);
367
368 if (cleanupTimer == null && call.getRpcTimeout() > 0) {
369 cleanupTimer = client.newTimeout(timeoutTask, call.getRpcTimeout(), TimeUnit.MILLISECONDS);
370 }
371 if (!connected) {
372 return call;
373 }
374 }
375 writeRequest(call);
376 return call;
377 }
378
379 AsyncCall removePendingCall(int id) {
380 synchronized (pendingCalls) {
381 return pendingCalls.remove(id);
382 }
383 }
384
385
386
387
388
389
390
391 private ChannelFuture writeChannelHeader(Channel channel) throws IOException {
392 RPCProtos.ConnectionHeader header = getChannelHeader(authMethod);
393 int totalSize = IPCUtil.getTotalSizeWhenWrittenDelimited(header);
394
395 ByteBuf b = channel.alloc().directBuffer(totalSize);
396
397 b.writeInt(header.getSerializedSize());
398 b.writeBytes(header.toByteArray());
399
400 return channel.writeAndFlush(b);
401 }
402
403 private byte[] getChannelHeaderBytes(AuthMethod authMethod) {
404 RPCProtos.ConnectionHeader header = getChannelHeader(authMethod);
405 ByteBuffer b = ByteBuffer.allocate(header.getSerializedSize() + 4);
406 b.putInt(header.getSerializedSize());
407 b.put(header.toByteArray());
408 return b.array();
409 }
410
411 private RPCProtos.ConnectionHeader getChannelHeader(AuthMethod authMethod) {
412 RPCProtos.ConnectionHeader.Builder headerBuilder = RPCProtos.ConnectionHeader.newBuilder()
413 .setServiceName(serviceName);
414
415 RPCProtos.UserInformation userInfoPB = buildUserInfo(ticket.getUGI(), authMethod);
416 if (userInfoPB != null) {
417 headerBuilder.setUserInfo(userInfoPB);
418 }
419
420 if (client.codec != null) {
421 headerBuilder.setCellBlockCodecClass(client.codec.getClass().getCanonicalName());
422 }
423 if (client.compressor != null) {
424 headerBuilder.setCellBlockCompressorClass(client.compressor.getClass().getCanonicalName());
425 }
426
427 headerBuilder.setVersionInfo(ProtobufUtil.getVersionInfo());
428 return headerBuilder.build();
429 }
430
431
432
433
434
435 private void writeRequest(final AsyncCall call) {
436 try {
437 final RPCProtos.RequestHeader.Builder requestHeaderBuilder = RPCProtos.RequestHeader
438 .newBuilder();
439 requestHeaderBuilder.setCallId(call.id).setMethodName(call.method.getName())
440 .setRequestParam(call.param != null);
441
442 if (Trace.isTracing()) {
443 Span s = Trace.currentSpan();
444 requestHeaderBuilder.setTraceInfo(TracingProtos.RPCTInfo.newBuilder()
445 .setParentId(s.getSpanId()).setTraceId(s.getTraceId()));
446 }
447
448 ByteBuffer cellBlock = client.buildCellBlock(call.controller.cellScanner());
449 if (cellBlock != null) {
450 final RPCProtos.CellBlockMeta.Builder cellBlockBuilder = RPCProtos.CellBlockMeta
451 .newBuilder();
452 cellBlockBuilder.setLength(cellBlock.limit());
453 requestHeaderBuilder.setCellBlockMeta(cellBlockBuilder.build());
454 }
455
456 if (call.controller.getPriority() != PayloadCarryingRpcController.PRIORITY_UNSET) {
457 requestHeaderBuilder.setPriority(call.controller.getPriority());
458 }
459 requestHeaderBuilder.setTimeout(call.rpcTimeout > Integer.MAX_VALUE ?
460 Integer.MAX_VALUE : (int)call.rpcTimeout);
461 RPCProtos.RequestHeader rh = requestHeaderBuilder.build();
462
463 int totalSize = IPCUtil.getTotalSizeWhenWrittenDelimited(rh, call.param);
464 if (cellBlock != null) {
465 totalSize += cellBlock.remaining();
466 }
467
468 ByteBuf b = channel.alloc().directBuffer(4 + totalSize);
469 try (ByteBufOutputStream out = new ByteBufOutputStream(b)) {
470 call.callStats.setRequestSizeBytes(IPCUtil.write(out, rh, call.param, cellBlock));
471 }
472
473 channel.writeAndFlush(b).addListener(new CallWriteListener(this, call.id));
474 } catch (IOException e) {
475 close(e);
476 }
477 }
478
479
480
481
482
483 private void setupAuthorization() throws IOException {
484 SecurityInfo securityInfo = SecurityInfo.getInfo(serviceName);
485 this.useSasl = client.userProvider.isHBaseSecurityEnabled();
486
487 this.token = null;
488 if (useSasl && securityInfo != null) {
489 AuthenticationProtos.TokenIdentifier.Kind tokenKind = securityInfo.getTokenKind();
490 if (tokenKind != null) {
491 TokenSelector<? extends TokenIdentifier> tokenSelector = TOKEN_HANDDLERS.get(tokenKind);
492 if (tokenSelector != null) {
493 token = tokenSelector.selectToken(new Text(client.clusterId),
494 ticket.getUGI().getTokens());
495 } else if (LOG.isDebugEnabled()) {
496 LOG.debug("No token selector found for type " + tokenKind);
497 }
498 }
499 String serverKey = securityInfo.getServerPrincipal();
500 if (serverKey == null) {
501 throw new IOException("Can't obtain server Kerberos config key from SecurityInfo");
502 }
503 this.serverPrincipal = SecurityUtil.getServerPrincipal(client.conf.get(serverKey),
504 address.getAddress().getCanonicalHostName().toLowerCase());
505 if (LOG.isDebugEnabled()) {
506 LOG.debug("RPC Server Kerberos principal name for service=" + serviceName + " is "
507 + serverPrincipal);
508 }
509 }
510
511 if (!useSasl) {
512 authMethod = AuthMethod.SIMPLE;
513 } else if (token != null) {
514 authMethod = AuthMethod.DIGEST;
515 } else {
516 authMethod = AuthMethod.KERBEROS;
517 }
518
519 if (LOG.isDebugEnabled()) {
520 LOG.debug(
521 "Use " + authMethod + " authentication for service " + serviceName + ", sasl=" + useSasl);
522 }
523 reloginMaxBackoff = client.conf.getInt("hbase.security.relogin.maxbackoff", 5000);
524 }
525
526
527
528
529
530
531
532 private RPCProtos.UserInformation buildUserInfo(UserGroupInformation ugi, AuthMethod authMethod) {
533 if (ugi == null || authMethod == AuthMethod.DIGEST) {
534
535 return null;
536 }
537 RPCProtos.UserInformation.Builder userInfoPB = RPCProtos.UserInformation.newBuilder();
538 if (authMethod == AuthMethod.KERBEROS) {
539
540 userInfoPB.setEffectiveUser(ugi.getUserName());
541 } else if (authMethod == AuthMethod.SIMPLE) {
542
543 userInfoPB.setEffectiveUser(ugi.getUserName());
544 if (ugi.getRealUser() != null) {
545 userInfoPB.setRealUser(ugi.getRealUser().getUserName());
546 }
547 }
548 return userInfoPB.build();
549 }
550
551
552
553
554
555
556 private void createPreamble(ByteBuf byteBuf, AuthMethod authMethod) {
557 byteBuf.writeBytes(HConstants.RPC_HEADER);
558 byteBuf.writeByte(HConstants.RPC_CURRENT_VERSION);
559 byteBuf.writeByte(authMethod.code);
560 }
561
562 private void close0(Throwable e) {
563 List<AsyncCall> toCleanup;
564 synchronized (pendingCalls) {
565 if (closed) {
566 return;
567 }
568 closed = true;
569 toCleanup = new ArrayList<AsyncCall>(pendingCalls.values());
570 pendingCalls.clear();
571 }
572 IOException closeException = null;
573 if (e != null) {
574 if (e instanceof IOException) {
575 closeException = (IOException) e;
576 } else {
577 closeException = new IOException(e);
578 }
579 }
580
581 if (LOG.isDebugEnabled() && closeException != null) {
582 LOG.debug(name + ": closing ipc connection to " + address, closeException);
583 }
584 if (cleanupTimer != null) {
585 cleanupTimer.cancel();
586 cleanupTimer = null;
587 }
588 for (AsyncCall call : toCleanup) {
589 call.setFailed(closeException != null ? closeException
590 : new ConnectionClosingException(
591 "Call id=" + call.id + " on server " + address + " aborted: connection is closing"));
592 }
593 channel.disconnect().addListener(ChannelFutureListener.CLOSE);
594 if (LOG.isDebugEnabled()) {
595 LOG.debug(name + ": closed");
596 }
597 }
598
599
600
601
602
603 public void close(final Throwable e) {
604 client.removeConnection(this);
605
606
607 if (channel.eventLoop().inEventLoop()) {
608 close0(e);
609 } else {
610 channel.eventLoop().execute(new Runnable() {
611 @Override
612 public void run() {
613 close0(e);
614 }
615 });
616 }
617 }
618
619
620
621
622 private void cleanupCalls() {
623 List<AsyncCall> toCleanup = new ArrayList<AsyncCall>();
624 long currentTime = EnvironmentEdgeManager.currentTime();
625 long nextCleanupTaskDelay = -1L;
626 synchronized (pendingCalls) {
627 for (Iterator<AsyncCall> iter = pendingCalls.values().iterator(); iter.hasNext();) {
628 AsyncCall call = iter.next();
629 long timeout = call.getRpcTimeout();
630 if (timeout > 0) {
631 if (currentTime - call.getStartTime() >= timeout) {
632 iter.remove();
633 toCleanup.add(call);
634 } else {
635 if (nextCleanupTaskDelay < 0 || timeout < nextCleanupTaskDelay) {
636 nextCleanupTaskDelay = timeout;
637 }
638 }
639 }
640 }
641 if (nextCleanupTaskDelay > 0) {
642 cleanupTimer = client.newTimeout(timeoutTask, nextCleanupTaskDelay, TimeUnit.MILLISECONDS);
643 } else {
644 cleanupTimer = null;
645 }
646 }
647 for (AsyncCall call : toCleanup) {
648 call.setFailed(new CallTimeoutException("Call id=" + call.id + ", waitTime="
649 + (currentTime - call.getStartTime()) + ", rpcTimeout=" + call.getRpcTimeout()));
650 }
651 }
652
653
654
655
656
657 public boolean isAlive() {
658 return channel.isOpen();
659 }
660
661
662
663
664
665
666 private synchronized boolean shouldAuthenticateOverKrb() throws IOException {
667 UserGroupInformation loginUser = UserGroupInformation.getLoginUser();
668 UserGroupInformation currentUser = UserGroupInformation.getCurrentUser();
669 UserGroupInformation realUser = currentUser.getRealUser();
670 return authMethod == AuthMethod.KERBEROS && loginUser != null &&
671
672 loginUser.hasKerberosCredentials() &&
673
674
675 (loginUser.equals(currentUser) || loginUser.equals(realUser));
676 }
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697 private void handleSaslConnectionFailure(final int currRetries, final Throwable ex,
698 final UserGroupInformation user) throws IOException, InterruptedException {
699 user.doAs(new PrivilegedExceptionAction<Void>() {
700 @Override
701 public Void run() throws IOException, InterruptedException {
702 if (shouldAuthenticateOverKrb()) {
703 if (currRetries < MAX_SASL_RETRIES) {
704 LOG.debug("Exception encountered while connecting to the server : " + ex);
705
706 if (UserGroupInformation.isLoginKeytabBased()) {
707 UserGroupInformation.getLoginUser().reloginFromKeytab();
708 } else {
709 UserGroupInformation.getLoginUser().reloginFromTicketCache();
710 }
711
712
713 return null;
714 } else {
715 String msg = "Couldn't setup connection for "
716 + UserGroupInformation.getLoginUser().getUserName() + " to " + serverPrincipal;
717 LOG.warn(msg);
718 throw (IOException) new IOException(msg).initCause(ex);
719 }
720 } else {
721 LOG.warn("Exception encountered while connecting to " + "the server : " + ex);
722 }
723 if (ex instanceof RemoteException) {
724 throw (RemoteException) ex;
725 }
726 if (ex instanceof SaslException) {
727 String msg = "SASL authentication failed."
728 + " The most likely cause is missing or invalid credentials." + " Consider 'kinit'.";
729 LOG.fatal(msg, ex);
730 throw new RuntimeException(msg, ex);
731 }
732 throw new IOException(ex);
733 }
734 });
735 }
736
737 public int getConnectionHashCode() {
738 return ConnectionId.hashCode(ticket, serviceName, address);
739 }
740
741 @Override
742 public int hashCode() {
743 return getConnectionHashCode();
744 }
745
746 @Override
747 public boolean equals(Object obj) {
748 if (obj instanceof AsyncRpcChannel) {
749 AsyncRpcChannel channel = (AsyncRpcChannel) obj;
750 return channel.hashCode() == obj.hashCode();
751 }
752 return false;
753 }
754
755 @Override
756 public String toString() {
757 return this.address.toString() + "/" + this.serviceName + "/" + this.ticket;
758 }
759
760
761
762
763 private static final class CallWriteListener implements ChannelFutureListener {
764 private final AsyncRpcChannel rpcChannel;
765 private final int id;
766
767 public CallWriteListener(AsyncRpcChannel asyncRpcChannel, int id) {
768 this.rpcChannel = asyncRpcChannel;
769 this.id = id;
770 }
771
772 @Override
773 public void operationComplete(ChannelFuture future) throws Exception {
774 if (!future.isSuccess()) {
775 AsyncCall call = rpcChannel.removePendingCall(id);
776 if (call != null) {
777 if (future.cause() instanceof IOException) {
778 call.setFailed((IOException) future.cause());
779 } else {
780 call.setFailed(new IOException(future.cause()));
781 }
782 }
783 }
784 }
785 }
786 }