Hutch & RabbitMQ: set timeout on re-queued messages - ruby-on-rails

When my Hutch consumer loses connection to a database I would like to requeue all the messages I got and try to handle (and save to the DB) them later.
I found I can use requeue! method like this in my consumer:
def process(message)
handle_message(message)
rescue ActiveRecord::ConnectionNotEstablished => error
Rails.logger.warn("Connection to database is broken: #{error}")
requeue!
ensure
::ActiveRecord::Base.clear_active_connections!
end
end
But then I will get that message instantly back from Rabbit, and so, my consumer stuck in trying to process this message when it's obviously can't be saved to the DB.
Is it possible to put a timeout on either Hutch or RabbitMQ site in this situation?

As it was answered here https://github.com/gocardless/hutch/issues/161
Not without using Scheduled delivery exchange, or abusing TTL and dead lettering.

Related

Raid doesn't receive C_ChatInfo.SendAddonMessage

I'm making this addons that have to send to the raid my interrupt cooldown.
The problem is that whenever i send a message to the raid i am the only one that receive it.
This is the code that send the message:
C_ChatInfo.SendAddonMessage("KickRotation",string.format( "%0.2f",remainingCd ), "RAID")
This is the event handler:
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("CHAT_MSG_ADDON")
frame:SetScript("OnEvent", function(self, event, ...)
local prefix, msg, msgType, sender = ...;
if event == "CHAT_MSG_ADDON" then
if prefix == "KickRotation" then
print("[KickRotation]" ..tostring(sender) .." potrĂ  interrompere tra: " ..msg);
end
end
if event == "PLAYER_ENTERING_WORLD" then
print("[KickRotation] v0.1 by Galfrad")
end
end)
Basically when the message is sended it is printed only to me.
Network messages are handled and transferred to the recipient channel (in this case, Raid Group) by the server. The reason that you are seeing the message locally, but the other people do not see it is that the message will be handled on the local system (sender) to reduce the repetition of data transmit.
Server however, only accepts and sends messages that are registered to it.
Therefore, you must first register your add-on messages to the server so the other players in the requested channel be able to receive it.
First, register your add-on messages with the name you have given already (But be sure to call the registration method only once per client):
local success = C_ChatInfo.RegisterAddonMessagePrefix("KickRotation") -- Addon name.
Next, check if your message was accepted and registered to the server. In case success is set to false (failure), you may want to handle proper warning messages and notifications to the user. The case of failure means that either server has disabled add-on messages or you have reached the limit of add-on message registrations.
Finally, send your message again check if it did not fail.
if not C_ChatInfo.SendAddonMessage("KickRotation",string.format( "%0.2f",remainingCd ), "RAID") then
print("[KickRotation] Failed to send add-on message, message rejected by the server.")
end

Rails 5 ActionCable setup and workflow

I am new to ActionCable and sockets and trying to implement some real time features.
I successfully implemented real time notifications functionality (basic one) into my app, however there are a couple of things on which i spent some time to understand.
My Real time notifications code:
The authentification process:
# Connection.rb (using Devise)
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = env['warden'].user
reject_unauthorized_connection unless self.current_user
logger.add_tags 'ActionCable', current_user.email
end
end
end
Notification Channel:
class NotificationsChannel < ApplicationCable::Channel
def subscribed
stream_from "notification_channel_#{current_user.id}"
end
def unsubscribed
end
end
Notifications.coffee
App.notifications = App.cable.subscriptions.create "NotificationsChannel",
connected: ->
disconnected: ->
received: (data) ->
this.update_counter(data.counter)
# if use_sound is "true" play a mp3 sound
# use_sound is sent ("true" or "false") because when a notification is destroyed we don't want to use sound only update counter
if data.use_sound == "true"
$("#sound-play").get(0).play()
# Update Notifications Counter and change color
update_counter: (counter) ->
$("#notifications-counter").html("#{counter}")
if counter > 0
$("#notifications-counter").css('color','#FFA5A5')
else
$("#notifications-counter").css('color','white')
And there is the Job file where we broadcast to the channel with Notifications Counter
Note: I only send notifications count because in the nav-bar when the user clicks the (ex. 2 Notifications) - button, a dropdown is toggled and populated via AJax call.
My questions are:
Is it normal to have all the time 'GET /cable' requests with the response 'An unauthorized connection attempt was rejected' when a user is not logged in ( I understand that the current_user in the Connect.rb file is set from the cookie, but when a user is on ex Home page, Logg-in page, etc.. basically not authenticated.
In the server logs I can see every 1,2 seconds the GET /cable
Is this normal? I'm thinking that somehow I have to stop this if the user logs out or something.
When I start the server I get the following error for like 10 seconds till the server starts in the browsers console. Since the notifications work, I don't know if this is a problem of not, but an error it is.
WebSocket connection to 'ws://localhost:3000/cable' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED
I even tried setting config.action_cable.mount_path = "/cable" or "ws://localhost:3000/cable" and did not fix it.
Next I want to implement a chat, where 2 users can have a private conversation, is the "current_user" defined in the Connection.rb 'secure', how can I add authorization to ActionCable or something like. In the rest of my app I'm using CanCan Gem and works perfectly.
Thank you very much for the time invested in reading the question. If you have answers for my questions I will really appreciate them. And if you have some directions to give or tips for building a complex and secure chat with ActionCable I would love to hear them. Thank you!
I think what you want to be looking at is the cable.js file where the createConsumer function is called.
Creating a consumer is what subscribers a user to the channel. I think you need to focus on when this code gets called.
You are using user authentication's presence to reject unauthorised use.
This is very good, however not so great when the JS consumer is created for any visitor (i.e. cable.js is loaded into application.js for all pages).
What you can do is use content_for into a seperate layout for authenticated users that calls the cable.js to create a consumer and ensure that cable.js does not get included into unauthorised pages.

ActionCable unsubscribe callback not working when iOS client send "unsubscribe"

Remote iOS client successfully connects to me, send subscribe command (it works fine), but on "unsubscribe" command I get next error:
Unsubscribing from channel: {"channel":"Assessor::StationChannel", "station_id": 1}
Could not execute command from {"command"=>"unsubscribe", "identifier"=>"{\"channel\":\"Assessor::StationChannel\", \"station_id\": 1}"}) [NoMethodError - undefined method `unsubscribe_from_channel' for nil:NilClass]: /app/vendor/bundle/ruby/2.2.0/gems/actioncable-5.0.0/lib/action_cable/connection/subscriptions.rb:44:in `remove_subscription' | /app/vendor/bundle/ruby/2.2.0/gems/actioncable-5.0.0/lib/action_cable/connection/subscriptions.rb:40:in `remove' | /app/vendor/bundle/ruby/2.2.0/gems/actioncable-5.0.0/lib/action_cable/connection/subscriptions.rb:16:in `execute_command' | /app/vendor/bundle/ruby/2.2.0/gems/actioncable-5.0.0/lib/action_cable/connection/base.rb:88:in `dispatch_websocket_message' | /app/vendor/bundle/ruby/2.2.0/gems/actioncable-5.0.0/lib/action_cable/server/worker.rb:58:in `block in invoke'
Subscribe message format:
{"command": "subscribe", "identifier": "{\"channel\":\"Assessor::StationChannel\", \"station_id\": 1}"}
Unsubscribe message format:
{"command": "unsubscribe", "identifier": "{\"channel\":\"Assessor::StationChannel\", \"station_id\": 1}"}
I cannot reproduce this problem on localhost, so maybe somebody can help me?
I saw a similar error. I was trying to unsubscribe via the client (JS). I eventually figured out it was because the javascript to .remove(subscription) takes the subscription and not the subscription identifier.
This is how I got it to work without error. Perhaps it will help you find out why you are getting the error from the server side.
subscription = App.cable.subscriptions.subscriptions[0]
App.cable.subscriptions.remove(subscription);
(Note, I'm just pulling the first subscription from the array, TODO: Search for the subscription I want to remove)
Here is the bug I was seeing and how I eventually found the source code/answer. I ran these from the webclient console:
App.cable.subscriptions.create({channel: "RoomChannel", room_id: 2})
That line works and I get a "... is transmitting the subscription confirmation" on stdout for rails s
App.cable.subscriptions.remove({channel: "RoomChannel", room_id: 2})
That line blows up, yells at my kids, and insults my wife which looks like:
[NoMethodError - undefined method `unsubscribe_from_channel' for nil:NilClass]: /usr/local/lib64/ruby/gems/2.3.0/gems/actioncable-5.0.0.1/lib/action_cable/connection/subscriptions.rb:44:in `remove_subscription'
I also noted the following line before the crash.
Unsubscribing from channel:
The code to produce that is: logger.info "Unsubscribing from channel: #{data['identifier']}". Which means it wasn't finding the data['identifier']. So I started debugging and I see that line 88 of base.rb in actioncable only gets {"command":"unsubscribe"} and not something like {"command":"unsubscribe", "identifier":" (channel name here)}
Which brought me to action_cable.js. (I would have started here, but I hate JS.). Here was my problem: function(subscription). I was sending the identifier and not the subscription object.
Subscriptions.prototype.remove = function(subscription) {
this.forget(subscription);
if (!this.findAll(subscription.identifier).length) {
this.sendCommand(subscription, "unsubscribe");
}
return subscription;
};
App.cable.subscriptions.create({channel: "RoomChannel", room_id: 2}) returns a subscription object you have to pass that into the remove function
var subscription = App.cable.subscriptions.create({channel: "RoomChannel", room_id: 2});
Then later
App.cable.subscriptions.remove(subscription);
After several tries I eventually figured it out. Late reply but this worked for me hope it does for you.
App["identifier"].disconnected() #e.g App["chat_4"].disconnect()
=> YourChannel stopped streaming from chat_4 #rails terminal
Above is the line to stop the streaming and below is how you subscribe to the channel
App['chat' + id] = App.cable.subscriptions.create({channel:
'YourChannel', chat_id: id}, {
disconnected: function () {
App.cable.subscriptions.remove(this)
},
)}
I know this is an old question, but I suffered from the same issue. None of the answers above worked for me and in fact, at least in Rails 5.0.1, they are incorrect. It is a frontend issue all right, but here's why it doesn't matter whether you call App.yourChannelName.unsubscribe() or App.yourChannelName.remove(App.yourChannelName)
Eg. if you have something like this (example code is in coffeescript, so ignore the lack of vars and other stuff from vanilla JS):
App.yourChannel = App.cable.subscriptions.create({channel: 'YourChannel', id: id})
...
// do stuff, execute callbacks, whatnot
...
// try to execute unsubscribe
App.yourChannel.unsubscribe()
The .unsubscribe() is a method on Subscription prototype which only does return this.consumer.subscriptions.remove(this)
The this.consumer.subscriptions returns the instance of your subscription, in example above, it would be App.yourChannel and calls .remove method with the instance of Subscription - ie. with App.yourChannel
So App.yourChannel.unsubscribe() is the same as calling App.cable.subscriptions.remove(App.yourChannel) (or whichever variable you choose to store the instance of Subscription in.
I have also been seeing the same error as OP, except that in my case, it was caused by App.yourChannel.unsubscribe() being called two times - the first time it was called immediately after I received specific data via the channel and the second time was due to a custom cleanup being run in a specific scenario before the App.yourChannel was re-subscribed.
So if you see a similar error, I suggest you look at the server logs.
You'll probably see something like
Registered connection (some-id) <-- initial subscription
YourChannel is transmitting the subscription confirmation
...
// other stuff while user is subscribed to the channel
...
Unsubscribing from channel: {"channel":"YourChannel","id":"some-id"} <-- initial unsubscribe call
YourChannel stopped streaming from your_channel_some-id
// some other requests potentially, perhaps some DB queries
...
// there are no requests to subscribe to the channel with the some-id, eg. you won't see this
// Registered connection (some-id)
// YourChannel is transmitting the subscription confirmation
Unsubscribing from channel: {"channel":"YourChannel","id":"some-id"} <-- duplicated unsubscribe call
Could not execute command from {"command"=>"unsubscribe", "identifier"=>"{\"channel\":\"YourChannel\",\"id\":\"some-id\"}"}) [NoMethodError - undefined method `unsubscribe_from_channel' for nil:NilClass]:
Basically, the user subscribes, unsubscribes, then tries to unsubscribe again (even though they are not subscribed to that channel anymore)

Rails 4 live stream + subscription based updates

I have a few questions around using ActionController::Live and streaming notifications to users.
Is there a way to tell if/when a stream is closed or no longer writable?
Is there a reason to manually close the stream in your action? I've seen some examples where they would explicitly close their stream, but watching the browser, it seems that it handles that for me?
I have a user notification class that my actions can subscribe to. In the create action of my users controller I tell the notification class to broadcast the new user.
In order to facilitate this, I've had to leave the stream open. I also have no idea when to unsubscribe from the user notifier as I don't know when the stream closes (via the browser initiating it).
#EventsController
def users
response.headers['Content-Type'] = 'text/event-stream'
sse = SSE.new(response.stream)
sse.write({}, event: :init)
UserNotifier.sub { |user| sse.write({ name: user.name, id: user.id }, event: :new_user ) }
end
So this just leaves a persistant connection open and then once something triggers the broadcast message on the UserNotifier, any one subbed to it will have it's block triggered.
The only issue again, is that I have no idea when to unsubscribe and in my block you see that I just explicitly call sse.write (wraps the response stream), which in turn blindly calls stream.write without knowing its status.
update
I've noticed some people rescue an IOError exception as a way to tell when a client has disconnected. Is that really the best way?
rescue IOError
UserNotifier.unsub(...)
...
You can use ensure to ensure that your stream gets closed once writing has failed or the connection has closed or something has gone amiss:
def your_action
response.headers['Content-Type'] = 'text/event-stream'
begin
# Do your writing here: response.stream.write("data: ...\n\n")
rescue IOError
# When the client disconnects, writing will fail, throwing an IOError
ensure
response.stream.close
end
end

sync call from process with many incoming msgs

Need to implement sync call from proces which receives many incoming messages from other processes. Problem in distinguish - when msg in return to call arrived. Do i need to spawn additional process for extracting msgs from queue into buffer while return msg not encountered and then send it to main process and after it every else accepted.
The trick is to use a reference as a token for replication:
replicate() ->
{ok, Token} = db:ask_replicate(...),
receive
{replication_completed, Token} ->
ok
end
where Token is created with a call to make_ref(). Since no other message will match Token, you are safe. Other messages will be placed in the mailbox for later scrutiny.
However, the above solution does not take process crashes into account. You need a monitor on the DB server as well. The simplest way to get the pattern right is to let the mediator be a gen_server. Alternatively, you can read a chapter in LearnYouSomeErlang: http://learnyousomeerlang.com/what-is-otp#the-basic-server look at the synchronous call in the kitty_server.

Resources