Long running C++ NIF segfaults

Hi again @jhogberg, I’m implementing your suggested solution using an Agent, but the program still crashes when a call to createScaledImage, for the same SOPInstanceUID is made more than once at the same time.

Please take a look at the updated image function:

  def image(conn, params) do                                                                                                                                                                                                                 
    sopinstanceuid = params["sopinstanceuid"]                                                                                                                                                                                                
    {width, ""} = Integer.parse(params["width"])                                                                                                                                                                                             
    {height, ""} = Integer.parse(params["height"])                                                                                                                                                                                           
    pid = ImageCache.get(sopinstanceuid)                                                                                                                                                                                                     
    if pid do                                                                                                                                                                                                                                
      IO.puts("====> CACHED! <====")                                                                                                                                                                                                         
      IO.puts "Pid: #{inspect pid}"                                                                                                                                                                                                          
      dcmff = Agent.get(pid, fn map -> Map.get(map, :ff) end)                                                                                                                                                                                
      scaledImage = Dcmtknif.createScaledImage(dcmff, width, height, 0, 0);                                                                                                                                                                  
      IO.puts("====> createScaledImage <====")                                                                                                                                                                                               
      wlImage = Dcmtknif.setMinMaxWindow(scaledImage)                                                                                                                                                                                        
      IO.puts("====> setMinMaxWindow <====")                                                                                                                                                                                                 
      pngimage = Dcmtknif.getPNG(wlImage)                                                                                                                                                                                                    
      IO.puts("====> getPNG <====")                                                                                                                                                                                                          
      conn                                                                                                                                                                                                                                   
      |> put_resp_content_type("image/png")                                                                                                                                                                                                  
      |> send_resp(200, pngimage)                                                                                                                                                                                                            
    else                                                                                                                                                                                                                                     
      im = ApiController.image(sopinstanceuid)                                                                                                                                                                                               
      IO.puts(im.imagepath)                                                                                                                                                                                                                  
      if File.exists?(im.imagepath) do                                                                                                                                                                                                       
        IO.puts("====> NOT CACHED, LOADING FROM FILE <====")                                                                                                                                                                                 
        dicomfile = im.imagepath                                                                                                                                                                                                             
        IO.puts("Antes de load()")                                                                                                                                                                                                           
                                                                                                                                                                                                                                             
        {:ok, pid} = Agent.start_link(fn -> %{} end)                                                                                                                                                                                         
        ImageCache.put(sopinstanceuid, pid)                                                                                                                                                                                                  
        Agent.update(pid, fn map -> Map.put(map, :ff, Dcmtknif.load(dicomfile)) end)                                                                                                                                                         
        dcmff = Agent.get(pid, fn map -> Map.get(map, :ff) end)                                                                                                                                                                              
        IO.puts("Antes de createScaledImage")                                                                                                                                                                                                
        scaledImage = Dcmtknif.createScaledImage(dcmff, width, height, 0, 0);                                                                                                                                                                
        IO.puts("Antes de setMinMaxWindow")                                                                                                                                                                                                  
        wlImage = Dcmtknif.setMinMaxWindow(scaledImage)                                                                                                                                                                                      
        pngimage = Dcmtknif.getPNG(wlImage)                                                                                                                                                                                                  
        conn                                                                                                                                                                                                                                 
        |> put_resp_content_type("image/png")                                                                                                                                                                                                
        |> send_resp(200, pngimage)                                                                                                                                                                                                          
      else                                                                                                                                                                                                                                   
        conn                                                                                                                                                                                                                                 
        |> send_resp(404, "File not found")                                                                                                                                                                                                  
      end                                                                                                                                                                                                                                    
    end                                                                                                                                                                                                                                      
  end

When call createScaledImage once everything is ok, but if the same image is requested more than once, the Agent returns the correct PID, but apparently it is not isolated. Here’s the output just before the crash:


====> CACHED! <====
[info] GET /image
====> CACHED! <====
[info] GET /image
[debug] Processing with Prueba3Web.PageController.image/2
  Parameters: %{"height" => "101", "sopinstanceuid" => "1.3.46.670589.11.76003.5.0.848.2022080115090557013", "width" => "101"}
  Pipelines: [:browser]
[debug] Processing with Prueba3Web.PageController.image/2
  Parameters: %{"height" => "101", "sopinstanceuid" => "1.3.46.670589.11.76003.5.0.848.2022080115090557013", "width" => "101"}
  Pipelines: [:browser]
Pid: #PID<0.649.0>
Pid: #PID<0.649.0>
====> createScaledImage <====
zsh: segmentation fault (core dumped)  mix phx.server

That’s not what I suggested. The idea is to run createScaledImage etc in the spawned process and send the image back. Your current implementation still runs all that in the requesting process, so it has the same problems as your old one.

The following ought to run it in the Agent instead. I haven’t checked if the code works or even compiles, but it will hopefully help you understand the general idea.

It would also be a good idea avoid using Agent for this since you may want these processes to die after they haven’t had any requests for a reasonable amount of time, or else they will hang around with their loaded images forever.

  def image(conn, params) do
    sopinstanceuid = params["sopinstanceuid"]
    {width, ""} = Integer.parse(params["width"])
    {height, ""} = Integer.parse(params["height"])
    pid = ImageCache.get(sopinstanceuid)
    if pid do
      IO.puts("====> CACHED! <====")
      IO.puts "Pid: #{inspect pid}"
      pngimage = Agent.get(pid,
                           fn dcmff ->
                              Dcmtknif.createScaledImage(dcmff, width, height, 0, 0)
                              Dcmtknif.setMinMaxWindow(scaledImage)
                              Dcmtknif.getPNG(wlImage)
                           end)
      conn
      |> put_resp_content_type("image/png")
      |> send_resp(200, pngimage)
    else
      im = ApiController.image(sopinstanceuid)
      IO.puts(im.imagepath)
      if File.exists?(im.imagepath) do
        IO.puts("====> NOT CACHED, LOADING FROM FILE <====")
        {:ok, pid} = Agent.start(fn -> Dcmtknif.load(im.imagepath) end)
        ImageCache.put(sopinstanceuid, pid)
        image(conn, params)
      else
        conn
        |> send_resp(404, "File not found")
      end
    end
  end

Thanks!!!, now the the segfault has dissapeared.