unixODBC:在Haskell线程

时间:2017-11-27 14:04:56

标签: multithreading haskell odbc unixodbc

我正在使用外部调用在Haskell中构建ODBC应用程序。在同一线程中分配的句柄上调用forkIO或forkOS线程(即无界或有界线程)中的odbc函数时,该函数返回无效的句柄错误。

在主线程中进行同样的调用是完美的。

1 个答案:

答案 0 :(得分:1)

我发现问题是由unixODBC中的错误引起的,我在此处记录了它:https://sourceforge.net/p/unixodbc/bugs/41/

简而言之,unixODBC使用句柄指向分配的内存来保存这些句柄的数据。由于句柄是32位整数,在64位体系结构上,它们被截断到最后一个重要的一半(对于x86处理器)。

因此,如果指针值小于2G,一切正常,但如果它超过2G(而不是因为符号位扩展而不是4G)那么unixODBC将无法定位数据结构对于句柄,将报告无效句柄。

当在主线程中从Haskell调用SQLAllocHandle时,分配的指针的值小于2G,因此一切正常。但是当从另一个线程(liftIO或liftOS)调用它时,分配的指针的值大于2G,因此在Haskell中使用unixODBC的多线程ODBC应用程序是不可能的,除非所有句柄分配都在主线程中完成。

我找到的解决方法基于以下事实:在主线程中我有一个等待线程中的工作完成的函数。我修改了该函数以监听句柄分配请求的通道,然后分配句柄并返回响应。

这是我用于workarround的示例代码:

-- All actions are ReaderT monad actions that share a global environment
-- for threads execution

-- | wait for worker threads to complete the work
waitForWorkToEnd :: (MonadIO m) => ReaderT MyEnvironment m ()
waitForWorkToEnd = do
  threadsCountVar <- asks threads_WorkerThreadsVar
  allocHandleChan <- asks threads_AllocHandleChan

  let waitIO = join $ atomically $ orElse (readTVar threadsCountVar >>= check . (<= 0) >> (return $ return ())) (allocHandleT allocHandleChan >>= \ io -> return (io >> waitIO))
  liftIO $ waitIO
  liftIO $ log $ fromString $ "all worker threads have finished"

-- | creates an IO action inside a STM monad to allocate a new handler in the current thread
allocHandleT :: (MonadIO m, MonadFail m) => TQueue (SQLSMALLINT, SQLINTEGER, TMVar SQLINTEGER) -> STM (m ())
allocHandleT chan = do
  (hType, hParent, retVar) <- readTQueue chan
  return $ allocHandle hType hParent >>= liftIO . atomically . (putTMVar retVar) 

-- | make a handle alloc request to the main thread and wait for result
allocHandleReq :: (MonadIO m, MonadFail m) => SQLSMALLINT -> SQLINTEGER -> ReaderT MyEnvironment m SQLINTEGER
allocHandleReq htype hparent = do
  allocHandleChan <- asks threads_AllocHandleChan
  resultVar       <- liftIO $ atomically $ newEmptyTMVar
  liftIO $ atomically $ writeTQueue allocHandleChan (htype, hparent, resultVar)
  liftIO $ atomically $ takeTMVar resultVar

-- allocHandle simply calls SQLAllocHandle and takes care of the diagnostics 
-- information; it is part of the sqlcli package you can find it here:
-- https://hub.darcs.net/mihaigiurgeanu/sqlcli
相关问题