1

I am trying to build RPC by using rabbitmq.

According to the tutorial for building RPC through rabbitmq http://www.rabbitmq.com/tutorials/tutorial-six-ruby.html, we can use the one reply-queue for each client and use correlation_id to map the response and request. I am confused about how the correlation_id is used?

Here is the issue I am running with, I would like to create two rpc calls synchronously from one client, using the same reply-queue with two different correlation id. However I don't know if this is the correct use case, since from what I read in the tutorial it seems to assume that each client is making the rpc call sequentially. (In this case, it becomes more confusing that why would we need the correlation_id here).

Here is the code example (the rpc_server.rb will be the same as the tutorial), for what I am trying to achieve. Hopefully it makes my question more clear.

The code block below wouldn't work, since the correlation_id is being overwrite by thr2 when we set it in thr1.

I wonder if there is anyway to modify it, to make it work? If we try to move the @reply_queue.subscribe block out of initialization and pass in different call_id, it still doesn't work since it seem like the @reply-queue will be locked while waiting for thr1 to finish.

Please let me know if the question is uncleared, thanks in advance for any of your response.

#!/usr/bin/env ruby
# encoding: utf-8

require "bunny"
require "thread"

conn = Bunny.new(:automatically_recover => false)
conn.start

ch   = conn.create_channel

class FibonacciClient
  attr_reader :reply_queue
  attr_accessor :response, :call_id
  attr_reader :lock, :condition

  def initialize(ch, server_queue)
    @ch             = ch
    @x              = ch.default_exchange

    @server_queue   = server_queue
    @reply_queue    = ch.queue("", :exclusive => true)

    @lock      = Mutex.new
    @condition = ConditionVariable.new
    that       = self

    @reply_queue.subscribe do |delivery_info, properties, payload|
      if properties[:correlation_id] == that.call_id
        that.response = payload.to_i
        that.lock.synchronize{that.condition.signal}
      end
    end
  end

  def call(n)
    self.call_id = self.generate_uuid

    @x.publish(n.to_s,
      :routing_key    => @server_queue,
      :correlation_id => call_id,
      :reply_to       => @reply_queue.name)

    lock.synchronize{condition.wait(lock)}
    response
  end

  protected

  def generate_uuid
    # very naive but good enough for code
    # examples
    "#{rand}#{rand}#{rand}"
  end
end

client   = FibonacciClient.new(ch, "rpc_queue")
thr1 = Thread.new{
    response1 = client.call(30)
    puts response1
}

thr2 = Thread.new{
    response2 = client.call(40)
    puts response2
}

ch.close
conn.close
Cheng Lun Chen
  • 105
  • 1
  • 7
  • I haven't figured out a good solution. The problem is that the code in docs is pretty simple an definitely not thread safe. Creating a dedicated client for each thread should work. – GorillaApe Apr 20 '23 at 09:56

1 Answers1

1

same problem here.

'use strict';

const config = require('./config')
const amqp = require('amqplib').connect('amqp://' + config.username + ':' + config.password + '@ali3')
const co = require('co')

const args = process.argv.slice(2)

if (args.length == 0) {
    console.log("Usage: rpc_client.js num");
    process.exit(1)
}

function generateUuid() {
    return Math.random().toString() +
        Math.random().toString() +
        Math.random().toString()
}

function* init(){
    let conn = yield amqp
    let ch = yield conn.createChannel()
    let cbQueue = yield ch.assertQueue('', {exclusive: true})
    return {"conn": conn, "channel": ch, "cbQueue": cbQueue}
}

function* sender(initConfig, msg, resHandler) {
    try {
        let ch = initConfig.channel
        let conn = initConfig.conn
        let cbQueue = initConfig.cbQueue

        const corr = generateUuid()
        console.log(' [x] [%s] Requesting fib(%d)',corr, msg)
        ch.consume(cbQueue.queue, (resultMsg) => {
            resHandler(resultMsg, corr, conn)
        })
        ch.sendToQueue('rpc_queue', new Buffer(msg.toString()), {"correlationId": corr, "replyTo": cbQueue.queue})
    }
    catch (ex) {
        console.warn("ex:", ex)
    }
}

function responseHandler(res, corr, conn) {
    console.log("corr: %s - %s", corr, res.content.toString());//important debug info
    if (res.properties.correlationId == corr)
    {
        console.log(' [.] Got %s', res.content.toString());
        //setTimeout(  () =>  {
        //    conn.close()
        //    process.exit(0)
        //}, 500);
    }
};

function onerror(err) {
    console.error(err.stack);
}

co(function*() {
    let num = parseInt(args[0])
    let initConfig = yield init();
    //let initConfig2 = yield init();
    yield [
        sender(initConfig, num.toString(), responseHandler),
        sender(initConfig, (num+3).toString(), responseHandler)
    ]

}, onerror)

dengwei@RMBAP:~/projects/github/rabbitmq-demo/rpc$ node rpc_client_gen.js 5 [x] [0.64227353665046390.20330130192451180.5467283953912556] Requesting fib(5) [x] [0.461023105075582860.22911424539051950.9930733679793775] Requesting fib(8) corr: 0.64227353665046390.20330130192451180.5467283953912556 - 5 [.] Got 5 corr: 0.461023105075582860.22911424539051950.9930733679793775 - 21 [.] Got 21

from the important debug here we can see: message has been sent from server to callback queue , and consume by client, check the uuid is set to the first message. so :

if (res.properties.correlationId == corr)

this line of code ignore the result.

seems that we can only init each time before send a single message.

no7dw
  • 505
  • 5
  • 13