public override void UserEventTriggered(IChannelHandlerContext ctx, object evt)
 {
     if (evt is IdleStateEvent idleStateEvent)
     {
         if (idleStateEvent.State == IdleState.ReaderIdle)
         {
             if (Logger().IsEnabled(LogLevel.Information))
             {
                 Logger().LogInformation($"channel {ctx.Channel} read idle.");
             }
             try
             {
                 string serverAddress = Utils.NetUtil.ToStringAddress(ctx.Channel.RemoteAddress);
                 _outerInstance.clientChannelManager.InvalidateObject(serverAddress, ctx.Channel).GetAwaiter().GetResult();
             }
             catch (Exception exx)
             {
                 Logger().LogError(exx, exx.Message);
             }
             finally
             {
                 _outerInstance.clientChannelManager.ReleaseChannel(ctx.Channel, _outerInstance.GetAddressFromContext(ctx)).GetAwaiter().GetResult();
             }
         }
         if (idleStateEvent == IdleStateEvent.WriterIdleStateEvent)
         {
             try
             {
                 if (Logger().IsEnabled(LogLevel.Debug))
                 {
                     Logger().LogDebug($"will send ping msg,channel {ctx.Channel}");
                 }
                 _outerInstance.SendAsyncRequest(ctx.Channel, HeartbeatMessage.PING).GetAwaiter().GetResult();
             }
             catch (Exception throwable)
             {
                 Logger().LogError(throwable, $"send request error: {throwable.Message}");
             }
         }
     }
 }
            public async Task Run()
            {
                while (true)
                {
                    lock (outerInstance.mergeLock)
                    {
                        try
                        {
                            Monitor.Wait(outerInstance.mergeLock, TimeSpan.FromMilliseconds(MAX_MERGE_SEND_MILLS));
                        }
                        catch (Exception)
                        {
                        }
                    }
                    outerInstance.isSending = true;

                    // send batch message is sync request, but there is no need to get the return value.
                    // Since the messageFuture has been created before the message is placed in basketMap,
                    // the return value will be obtained in ClientOnResponseProcessor.
                    // fast fail
                    foreach (var item in outerInstance.basketMap)
                    {
                        var address = item.Key;

                        if (item.Value.Count <= 0)
                        {
                            return;
                        }
                        var mergeMessage = new MergedWarpMessage();
                        while (item.Value.Count > 0)
                        {
                            RpcMessage msg = item.Value.Take();
                            mergeMessage.msgs.Add((AbstractMessage)msg.Body);
                            mergeMessage.msgIds.Add(msg.Id);
                        }
                        if (mergeMessage.msgIds.Count > 1)
                        {
                            PrintMergeMessageLog(mergeMessage);
                        }
                        IChannel sendChannel = null;
                        try
                        {
                            sendChannel = await outerInstance.clientChannelManager.AcquireChannel(address);

                            await outerInstance.SendAsyncRequest(sendChannel, mergeMessage);
                        }
                        catch (FrameworkException e)
                        {
                            if (e.Errcode == FrameworkErrorCode.ChannelIsNotWritable && sendChannel != null)
                            {
                                await outerInstance.DestroyChannel(address, sendChannel);
                            }
                            foreach (int msgId in mergeMessage.msgIds)
                            {
                                if (outerInstance._futures.TryRemove(msgId, out MessageFuture messageFuture) && messageFuture != null)
                                {
                                    messageFuture.ResultMessage = null;
                                }
                            }
                            Logger().LogError(e, $"client merge call failed: {e.Message}");
                        }
                    }

                    outerInstance.isSending = false;
                }
            }