zhangzhen

zhangzhen

I use phoenix/elixir to write a mini-system that monitors the status of tasks that Nextflow (a workflow engine) starts. I called the system br-tower. Nextflow calls a web callback, i.e. webhook, I implemented when the state of a job changes. Most of these http requests can be handled by br-tower, but a few requests cannot. I use wireshark to capture the tcp packages Nextflow sends to br-tower, in order to find out whether such requests have any json payload or not. Surprisingly, they all have json payload. In the figure below, such requests are framed in red. Besides, http get requests in-between don’t get any response back. In the console where mix phx.server ran, only [info] POST /webhooks/nextflow was printed out, and no Parameters part follows. It is very weird. I will be very grateful, if someone could help me to solve this problem.

Showing Posts 1 to 10

derek-zhou

derek-zhou

What you are doing is nothing out of ordinary. However, you may want to post more details, like console prints, router code, etc so other people can help you.

One thing I can think of is if you are posting from another origin, you need to make sure CSRF is not in your way. In any event, there should be error message in the console; no requests should be silently dropped.

zhangzhen

zhangzhen OP

@derek-zhou, Thank you!

the code for the router is listed below:

defmodule BrTowerWebuiWeb.Router do
  use BrTowerWebuiWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, {BrTowerWebuiWeb.LayoutView, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", BrTowerWebuiWeb do
    pipe_through :browser

    live "/", PageLive, :index
    get "/batches/:id", BatchController, :show

  end

  scope "/api", BrTowerWebuiWeb do
    pipe_through :api

    get "/batches/:batch_id/items/:id", ItemController, :show
  end

  scope "/webhooks", BrTowerWebuiWeb do
   pipe_through :api

   post "/nextflow", WebhookController, :nextflow
  end

  if Mix.env() in [:dev, :test] do
    import Phoenix.LiveDashboard.Router

    scope "/" do
      pipe_through :browser
      live_dashboard "/dashboard", metrics: BrTowerWebuiWeb.Telemetry
    end
  end
end

The screenshoot for the console is pasted below. Please ignore the text labelled on the figure because I thought there was no payload, but in reality payload was there.

derek-zhou

derek-zhou

So what is the problem? you did receive payload.

lud

lud

Do you have an unit test that works all the times, or that fails too?

Check the docs to implement such a test if you don’t already know how.

zhangzhen

zhangzhen OP

The problem is http requests with payload were rejected silently by Plug and didn’t reach for the corresponding controller action.

I don’t have any unit test for this case till now.

when the weird case happened, the v2ray service (sort of a proxy) was on. I suspect that the v2ray service is most likely to be the culprit. I will report back if I can determine what the cause is.

derek-zhou

derek-zhou

In your log after the first POST /webhooks/next/flow, there is a Sent 200 in 1 ms so at least it worked for once. If subsequent requests are not handled, it is most likely a bug in your program so your controller hangs.

If Plug decided to reject any request for whatever reason, ther will be print in the console.

zhangzhen

zhangzhen OP

The screenshot shows only a very tiny part of log info. Post requests after the part worked normally. In addition, a few GET /batches/[:batch_id] didn’t work either. I think that this has nothing to do with the code for the controller action.
The following code snippet is about the controller and the related parser. I cannot find where the bug lies indeed.

defmodule BrTowerWebuiWeb.WebhookController do
  use BrTowerWebuiWeb, :controller

  alias BrTowerWebui.WeblogParser
  alias BrTower.Core.Batch

  @topic "batch:"

  def nextflow(conn, data) do
    case WeblogParser.parse(data) do
      {:error, msg} ->
        conn
        |> put_status(:bad_request)
        |> json(%{message: "Error: #{msg}"})
      event ->
        batch = BrTower.dispatch(event)
        case batch do
          {:error, msg} ->
            conn
            |> put_status(:not_found)
            |> json(%{message: "Error: #{msg}"})
          _ ->
            item_dtos = batch.items
            |> Enum.map(fn {k, _v} -> Batch.build_item_dto(batch, k) end)
            Phoenix.PubSub.broadcast(BrTowerWebui.PubSub, @topic <> "#{batch.id}", %{id: batch.id, items: item_dtos})
            json(conn, %{message: "Event received"})
        end
    end
  end
end
defmodule BrTowerWebui.WeblogParser do
  alias BrTower.Core.Events.{BasicInfo, TaskInfo, PipelineStarted, PipelineCompleted, PipelineErrorOccurred, TaskSubmitted, TaskStarted, TaskCompleted}

  def parse(%{"runName" => run_name, "event" => event, "utcTime" => utc_time_str} = data) do
    run_id = Map.get(data, "runId")
    {:ok, utc_time, _} = DateTime.from_iso8601(utc_time_str)
    basic_info = BasicInfo.new(run_id, run_name, utc_time)

    case event do
      "started" ->
        batch_id = get_in(data, ["metadata", "parameters", "id_for_tower"])
        session_id = get_in(data, ["metadata", "workflow", "sessionId"])
        analysis_type = get_in(data, ["metadata", "parameters", "analysis_type"])
        mode = get_in(data, ["metadata", "parameters", "mode"])
               |> String.to_atom()
        case batch_id do
          nil ->
            {:error, "batch_id is missing"}
          _ ->
            PipelineStarted.new(basic_info, session_id, batch_id, analysis_type, mode)
        end
      "completed" ->
        batch_id = get_in(data, ["metadata", "parameters", "id_for_tower"])
        success = get_in(data, ["metadata", "workflow", "success"])
        analysis_type = get_in(data, ["metadata", "parameters", "analysisType"])
        case batch_id do
          nil ->
            {:error, "batch_id is missing"}
          _ ->
            batch_id = if is_nil(analysis_type), do: batch_id, else: "#{batch_id}_#{analysis_type}"
            PipelineCompleted.new(basic_info, batch_id, success)
        end
      "error" ->
        trace = Map.get(data, "trace")
        case trace do
          nil ->
            PipelineErrorOccurred.new(basic_info, nil)
          _ ->
            task_info = build_task_info(trace)
            case task_info.item_id do
              nil ->
                {:error, "item_id is missing"}
              _ ->
                PipelineErrorOccurred.new(basic_info, task_info)
            end
        end
      x when x in ["process_submitted", "process_started", "process_completed"] ->
        trace = Map.get(data, "trace")
        task_info = build_task_info(trace)
        case task_info.item_id do
          nil ->
            {:error, "item_id is missing"}
          _ ->
            case x do
              "process_submitted" ->
                TaskSubmitted.new(basic_info, task_info)
              "process_started" ->
                TaskStarted.new(basic_info, task_info)
              "process_completed" ->
                TaskCompleted.new(basic_info, task_info)
            end
        end
    end
  end

  def parse(data) do
    {:error, "invalid data: #{data}"}
  end

  defp build_task_info(trace) do
    process_name = Map.get(trace, "process")
    item_id = Map.get(trace, "tag")
    status = trace
             |> Map.get("status")
             |> String.to_atom
    TaskInfo.new(process_name, status, item_id)
  end
end
derek-zhou

derek-zhou

Other people cannot help you debug your code unless you can make a minimum reproducible case and put it on github.

Elixir gives you many tools to debug; you have a console, a remote iex shell, even a live dashboard. Also you will need to have some faith in Phoenix; it is much more likely the bug is in your side.

zhangzhen

zhangzhen OP

In my log, [info] POST /webhooks/nextflow was output by the Plug.Logger, and [debug] Processing with BrTowerWebuiWeb.WebhookController.nextflow/2 … was output by the phoenix_router_dispatch_start in the Phoenix.Logger. phoenix_router_dispatch_start is installed as the event handler for [:phoenix, :router_dispatch, :start]:
https://github.com/phoenixframework/phoenix/blob/60da3f0dc01b7e0eae0e5bd608431ead953ded5d/lib/phoenix/logger.ex#L128
This means dispatching a request to the corresponding controller action was not started because the related debug line was missing in my log, so I can conclude that this case is not caused by the bug that perhaps exists from my controller part.

Sorry, I cannot put my code on github because of the policy of our company, but i can give you the architectural design which is shown in the following figure:

voltone

voltone

Or, alternatively, the function you attached to :phoenix_router_dispatch_start has a problem and Telemetry removed it after that first invocation:

If the function fails (raises, exits or throws) then the handler is removed

I would start by putting some debug logs in the controller itself; Telemetry is perhaps not the best tool to rely on for debugging.

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews