Cloud Haskell навешивается навсегда при отправке сообщений ManagedProcess

Проблема

Здравствуйте! Я пишу в Cloud Haskell простую программу Server-Worker. Проблема в том, что когда я пытаюсь создать ManagedProcess, после шага обнаружения сервера мой пример вешает вечно даже при использовании callTimeout (который должен прерываться через 100 мс). Код очень прост, но я не могу найти с ним ничего плохого.

Я также разместил вопрос в списке рассылки, но, насколько я знаю сообщество SO, я могу ответить на этот вопрос намного быстрее. Если я получу ответ из списка рассылки, я также опубликую здесь.

Исходный код

Worker.hs:

{-# LANGUAGE DeriveDataTypeable        #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE DeriveGeneric             #-}
{-# LANGUAGE TemplateHaskell           #-}

module Main where

import Network.Transport     (EndPointAddress(EndPointAddress))
import Control.Distributed.Process hiding (call)
import Control.Distributed.Process.Platform hiding (__remoteTable)
import Control.Distributed.Process.Platform.Async
import Control.Distributed.Process.Platform.ManagedProcess
import Control.Distributed.Process.Platform.Time
import Control.Distributed.Process.Platform.Timer (sleep)
import Control.Distributed.Process.Closure (mkClosure, remotable)
import Network.Transport.TCP (createTransport, defaultTCPParameters)
import Control.Distributed.Process.Node hiding (call)
import Control.Concurrent (threadDelay)
import GHC.Generics (Generic)
import Data.Binary (Binary) 
import Data.Typeable (Typeable)
import Data.ByteString.Char8 (pack)
import System.Environment    (getArgs)

import qualified Server as Server

main = do
  [host, port, serverAddr] <- getArgs

  Right transport <- createTransport host port defaultTCPParameters
  node <- newLocalNode transport initRemoteTable

  let addr = EndPointAddress (pack serverAddr)
      srvID = NodeId addr

  _ <- forkProcess node $ do
    sid <- discoverServer srvID
    liftIO $ putStrLn "x"
    liftIO $ print sid
    r <- callTimeout sid (Server.Add 5 6) 100 :: Process (Maybe Double)
    liftIO $ putStrLn "x"
    liftIO $ threadDelay (10 * 1000 * 1000)


  threadDelay (10 * 1000 * 1000)
  return ()


discoverServer srvID = do
  whereisRemoteAsync srvID "serverPID"
  reply <- expectTimeout 100 :: Process (Maybe WhereIsReply)
  case reply of
    Just (WhereIsReply _ msid) -> case msid of
      Just sid -> return sid
      Nothing  -> discoverServer srvID
    Nothing                    -> discoverServer srvID

Server.hs:

{-# LANGUAGE DeriveDataTypeable        #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE DeriveGeneric             #-}
{-# LANGUAGE TemplateHaskell           #-}

module Server where

import Control.Distributed.Process hiding (call)
import Control.Distributed.Process.Platform hiding (__remoteTable)
import Control.Distributed.Process.Platform.Async
import Control.Distributed.Process.Platform.ManagedProcess
import Control.Distributed.Process.Platform.Time
import Control.Distributed.Process.Platform.Timer (sleep)
import Control.Distributed.Process.Closure (mkClosure, remotable)
import Network.Transport.TCP (createTransport, defaultTCPParameters)
import Control.Distributed.Process.Node hiding (call)
import Control.Concurrent (threadDelay)
import GHC.Generics (Generic)
import Data.Binary (Binary) 
import Data.Typeable (Typeable)


data Add = Add Double Double
  deriving (Typeable, Generic)
instance Binary Add

launchServer :: Process ProcessId
launchServer = spawnLocal $ serve () (statelessInit Infinity) server >> return () where
  server = statelessProcess { apiHandlers            = [ handleCall_ (\(Add x y) -> liftIO (putStrLn "!") >> return (x + y)) ]
                            , unhandledMessagePolicy = Drop
                            }


main = do
  Right transport <- createTransport "127.0.0.1" "8080" defaultTCPParameters
  node <- newLocalNode transport initRemoteTable
  _ <- forkProcess node $ do
    self <- getSelfPid
    register "serverPID" self

    liftIO $ putStrLn "x"
    mid <- launchServer
    liftIO $ putStrLn "y"
    r <- call mid (Add 5 6) :: Process Double
    liftIO $ print r
    liftIO $ putStrLn "z"
    liftIO $ threadDelay (10 * 1000 * 1000)
    liftIO $ putStrLn "z2"

  threadDelay (10 * 1000 * 1000)
  return ()

Мы можем запускать их следующим образом:

runhaskell Server.hs
runhaskell Worker.hs 127.0.0.2 8080 127.0.0.1:8080:0

Результаты

Когда мы запускаем программы, мы получили следующие результаты:

от сервера:

x
y
!
11.0 -- this one shows that inside the same process we were able to use the "call" function
z
-- waiting - all the output above were tests from inside the server now it waits for external messages

от Рабочего:

x
pid://127.0.0.1:8080:0:10 -- this is the process id of the server optained with whereisRemoteAsync 
-- waiting forever on the "callTimeout sid (Server.Add 5 6) 100" code!

Как оповещение - я узнал, что при отправке сообщений с send (из Control.Distributed.Process) и их возврату с помощью expect работает. Но отправляя их с call (из Control.Distributed.Process.Platform) и пытаясь восстановить их с помощью ManagedProcess обработчиков api - вечно call (даже используя callTimeout!)

Ответ 1

Ваш клиент получает исключение, которое вы не можете легко наблюдать, потому что вы запускаете своего клиента в forkProcess. Если вы хотите сделать это, это нормально, но тогда вам нужно отслеживать или ссылаться на этот процесс. В этом случае просто использовать runProcess было бы намного проще. Если вы это сделаете, вы увидите, что получите это исключение:

Worker.hs: trying to call fromInteger for a TimeInterval. Cannot guess units

callTimeout не принимает Integer, он принимает TimeInterval, которые строятся с функциями в модуле Time. Это псевдо-Num - на самом деле это не поддерживает InInger. Я бы подумал, что ошибка или, по крайней мере, плохая форма (в Haskell), но в любом случае способ исправить ваш код просто

r <- callTimeout sid (Server.Add 5 6) (milliSeconds 100) :: Process (Maybe Double)

Чтобы устранить проблему с вызовом клиента на сервер, вам необходимо зарегистрировать pid процесса сервера, который вы породили, а не основной процесс, с которого вы его породили, - например, изменить

self <- getSelfPid
register "serverPID" self

liftIO $ putStrLn "x"
mid <- launchServer
liftIO $ putStrLn "y"

к

mid <- launchServer
register "serverPID" mid
liftIO $ putStrLn "y"