CrustyRatFink
CrustyRatFink

Reputation: 534

ruby-graphql and/or actioncable dumping "core" on subscription

long shot here, but wonder if anyone can shed any light on why an @apollo/client (3.3.11) subscription from react (17.1) to a Rails (6.1.1) and graphql (1.12) API would cause rails to, what I can only describe as "dump core" (sorry, I'm an old guy).

The external symptoms are that when the client connects, I get pages and pages of logs that start with:

Started GET "/cable/" [WebSocket] for 172.27.0.1 at 2021-03-30 19:30:45 +0000
api_1  | Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
api_1  | GraphqlChannel is transmitting the subscription confirmation
api_1  | GraphqlChannel is transmitting the subscription confirmation
api_1  | GraphqlChannel#execute({"query"=>"subscription {\n  commentAdded {\n    id\n    title\n    __typename\n  }\n}\n", "variables"=>{}})
api_1  | GraphqlChannel#execute({"query"=>"subscription {\n  commentAdded {\n    id\n    title\n    __typename\n  }\n}\n", "variables"=>{}})
api_1  | Could not execute command from ({"command"=>"message", "identifier"=>"{\"channel\":\"GraphqlChannel\",\"channelId\":\"1788483aa6e\"}", "data"=>"{\"query\":\"subscription {\\n  commentAdded {\\n    id\\n    title\\n    __typename\\n  }\\n}\\n\",\"variables\":{},\"action\":\"execute\"}"}) 
[NoMethodError - undefined method `unpack' for {:query=>"subscription {\n  commentAdded {\n    id\n    title\n    __typename\n  }\n}\n", :context=>{:channel=>#<GraphqlChannel:0x000055b35c0503b0 @connection=#<ApplicationCable::Connection:0x000055b35c1b1308 @coder=ActiveSupport::JSON, 
@env={"rack.version"=>[1, 6], "rack.errors"=>#<IO:<STDERR>>, "rack.multithread"=>true, "rack.multiprocess"=>false, "rack.run_once"=>false, 
"SCRIPT_NAME"=>"/cable", "QUERY_STRING"=>"", "SERVER_PROTOCOL"=>"HTTP/1.1", "SERVER_SOFTWARE"=>"puma 5.2.2 Fettisdagsbulle", "GATEWAY_INTERFACE"=>"CGI/1.2", "REQUEST_METHOD"=>"GET", "REQUEST_PATH"=>"/cable", 
"REQUEST_URI"=>"/cable", "HTTP_VERSION"=>"HTTP/1.1", "HTTP_HOST"=>"localhost:3001"...

And then proceed to list about anything having to do with my computer, files, the state of the app, repeatedly... it's a lot.

So, normal queries and mutations work fine. The subscription is visible in graphiql, and, indeed, given the fact that I see:

[ActionCable] Broadcasting to graphql-event::commentAdded:: "{\"__gid__\":\"Z2lkOi8va29hbGEtYXBpL0NvbW1lbnQvZjQzNWQyOWUtODMxOC00YWY0LTg5ODktZDJiZDhkOTljYmQ4\"}"

in my Rails logs, I think things are mostly working inside Rails. (For instance, if I change the subscription name, it errors. The cable connection seems to be established to some degree in that I see pings in Chrome dev tools.)

However, the update never gets sent to the client.

The gql looks like:

const COMMENTS_SUBSCRIPTION = gql`
  subscription {
    commentAdded {
      id
      title
    }
  }
`;

Setting up the Apollo Client like this (relevant parts, as seen in the few docs available):

const link = ApolloLink.split(
  hasSubscriptionOperation,
  new ActionCableLink({cable}) as any,
  stdLink
);

And the subscription's set up in Rails, like so:

module Types
  class SubscriptionType < GraphQL::Schema::Object
    field :comment_added, type: Types::CommentType,
                          null: false,
                          description: "Subscription to push new comments as added"

    def comment_added
      object
    end
  end
end

Once that Big Barf happens, it sort of just takes the mutation, adds the thing, logs that it's broadcasting, and then... nothing. No sign of anything on the client. That leads me to believe that the subscription didn't actually get registered.

Any pointers on what to look for? Should I be able to somehow debug from a rails console?

Upvotes: 1

Views: 311

Answers (1)

CrustyRatFink
CrustyRatFink

Reputation: 534

OK, to answer my own question, this was a result of following a top-listed step-by-step for graphql subscriptions rails that shall remain nameless. It suggested setting up the channel as follows:

result = GraphqlRailsApiSchema.execute({
      query: query,
      context: context,
      variables: variables,
      operation_name: operation_name
    })

Now I know. Don't do that. Note how the entire argument is a hash. Diving into the graphql gem source, I saw that it expected a query string and **kwargs. In a nutshell, it was taking that hash and treating it like the query string argument (hence, trying to unpack it).

Instead, you can just do this:

result = GraphqlRailsApiSchema.execute(
      query,
      context: context,
      variables: variables,
      operation_name: operation_name
    )

"Careful what you read on the Internet." --Socrates

Upvotes: 3

Related Questions