Управляемый IBM MQ. Net Клиент v9.1.4 зависает при получении по SSL при работе на. Net Core 3.1 - PullRequest
4 голосов
/ 27 марта 2020

MQQueue.Get() операция останавливается на 5 минут при получении сообщения из QueueManager с использованием SSL на .NetCore 3.1. Один и тот же код работает нормально при работе в. Net Framework (v4.8) или без SSL в обеих средах выполнения.

Подробности:

  • Клиент: Управляемый клиент MQ от NuGet (IBMMQDotnetClient / v9.1.4)
  • Сервер: docker из тега ibmcom / mq: 9.1.4.0-r1 с базисной c конфигурацией SSL с использованием SSLCIPH(ANY_TLS12) SSLCAUTH(OPTIONAL)
  • Воспроизведено: . Net Ядро 3.1.2 на Windows и. Net Ядро 3.1.1 на Linux
  • MQ Logs: MqGet-framework48.zip , MqGet-core31.zip

Мои выводы:

  • Вложенный MqGet-core31 сообщает:
    1. Клиент отправляет запрос на получение сообщения в 09: 15: 52.199588
    2. Он получает ответ с телом сообщения почти сразу в 09: 15: 52.311050
    3. Затем он вызывает другую MQRcvThread.ReceiveOneTSH() для завершения sh операции get. Он вызывает MQTCPConnection.Receive() и останавливается на 5 минут до 09: 20: 52.412669
  • Возможная причина:
    • MQTCPConnection сохраняет оригинал * Экземпляр 1060 * и MQEncryptedSocket обертывают тот же socket через SslStream
    • Метод MQTCPConnection.Receive() опрашивает socket, а затем вызывает MQEncryptedSocket.Read()
    • Похоже что данные из socket считываются SslStream в MQEncryptedSocket до вызова метода poll() и poll() ожидает своего 5-минутного тайм-аута. Или любое другое состояние гонки?
    • Если я поставлю точку останова перед проблемным c poll() вызовом и задержу его на мгновение, то оно часто проходит без задержки!

Тестовый код:

Этот код заканчивается через ~ 2 секунды. Net Framework, но для его запуска требуется 5 минут и ~ 2 секунды. . Net Core. (Win или Linux)

// import server's CA
MQConnectionPool.ImportServerCertificateAuthorityIfNotPresent(CertMqTestCa);

Hashtable properties = new Hashtable {
    {MQC.HOST_NAME_PROPERTY, Mq1QmSslAnyTls12.Host},
    {MQC.PORT_PROPERTY, Mq1QmSslAnyTls12.Port},
    {MQC.CHANNEL_PROPERTY, Mq1QmSslAnyTls12.ChannelName},
    {MQC.USER_ID_PROPERTY, "admin"},
    {MQC.PASSWORD_PROPERTY, "changeit"},
    {MQC.CCSID_PROPERTY, 819},
    {MQC.SSL_CIPHER_SPEC_PROPERTY, "TLS_RSA_WITH_AES_256_CBC_SHA256"},
    {MQC.SSL_CERT_STORE_PROPERTY, "*USER"}
};
MQQueueManager queueManager;
using (new MQCcsidSetter(properties)) {
    queueManager = new MQQueueManager(null, properties);
}

string requestText = Guid.NewGuid().ToString();
string queueName = "SV.MqConnectionPoolTest";
// put message - works fine
using (MQQueue queue = queueManager.AccessQueue(queueName, MQC.MQOO_OUTPUT + MQC.MQOO_FAIL_IF_QUIESCING)) {
    byte[] requestBytes = Encoding.UTF8.GetBytes(requestText);
    MQMessage requestMessage = new MQMessage {Expiry = 3000, Priority = 4, CharacterSet = 1208, MessageType = MQC.MQMT_DATAGRAM};
    requestMessage.Write(requestBytes, 0, requestBytes.GetLength(0));
    queue.Put(requestMessage, new MQPutMessageOptions());
}

// get message back from the same queue
using (MQQueue queue = queueManager.AccessQueue(queueName, MQC.MQOO_INPUT_AS_Q_DEF + MQC.MQOO_FAIL_IF_QUIESCING)) {
    while (true) {
        MQMessage msg = new MQMessage();
        queue.Get(msg, new MQGetMessageOptions()); // <<= !!!IT IS DELAYED HERE!!!
        msg.Seek(0);
        string msgContent = msg.ReadString(msg.MessageLength);
        if (requestText.Equals(msgContent)) {
            break;
        }
    }
}

Дампы потока:

  • Поток получателя клиента MQ
SocketPal.Poll()
Socket.Poll()
MQTCPConnection.Receive() 
MQRcvThread.ReceiveBuffer()
MQRcvThread.ReceiveOneTSH()
MQRcvThread.Run()
ThreadHelper.ThreadStart_Context()
ExecutionContext.RunInternal()
ThreadHelper.ThreadStart()

Ожидание Poll(), но оно заканчивается 5-минутным (сердцебиение) тайм-аутом. Затем он переходит к this.network.Read() и немедленно получает правильные данные.

    this.socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, this.timeout);
    if (this.socket.Poll(this.timeout * 1000, SelectMode.SelectRead))
    {
      length2 = this.network.Read(cBuffer, offset1, length1);
  • Поток приложения
Monitor.Wait()
MQSession.ExchangeTSH() 
MQProxyQueue.RequestMessages()
MQProxyQueue.FlushQueue()
MQProxyQueue.ProxyMQGET()
MQFAP.zstMQGET()
MQFAP.MQGET()
MQDestination.Get()
MQDestination.Get()
MqConnectionPoolTest.TestMqDirect()

Ожидает entry.Reply для заполнения получающей веткой:

    while (entry.Reply == null) {
      Monitor.Wait((object) entry, this.rmtReqEntMaxPollTime, true);
      ...

Пожалуйста, кто-нибудь знает обходной путь или есть исправление?

1 Ответ

1 голос
/ 27 марта 2020

Вы не должны Get и ждать бесконечно, вы должны сделать это так:

const int TIMEOUTTIME = 20000;
try
{
    MQGetMessageOptions gmo = new MQGetMessageOptions();
    gmo.Options = MQC.MQGMO_WAIT;
    gmo.WaitInterval = TIMEOUTTIME;
    queue.Get(msg, gmo);
    msg.Seek(0);
    string msgContent = msg.ReadString(msg.MessageLength);
    if (requestText.Equals(msgContent)) {
        break;
    }
}
catch (MQException ex)
{
    if (ex.CompletionCode == MQC.MQCC_FAILED && ex.ReasonCode == MQC.MQRC_NO_MSG_AVAILABLE)
    {
        // Log on DEBUG level something like this:
        //    No message from <queue> after <TIMEOUTTIME / 1000> seconds, continue receiving");
        continue;
    }
    throw ex;
}

Чтобы сделать жизнь вашего программиста проще, я бы рекомендовал использовать XMS API .

...