Multithreading unixODBC:在Haskell线程中分配句柄时句柄无效

Multithreading unixODBC:在Haskell线程中分配句柄时句柄无效,multithreading,haskell,odbc,unixodbc,Multithreading,Haskell,Odbc,Unixodbc,我正在Haskell中使用外部调用构建一个ODBC应用程序。在同一线程中分配的句柄上调用forkIO或forkOS线程(即无界或有界线程)中的odbc函数时,该函数返回无效句柄错误 在主线程中进行相同的调用时,效果非常好。我发现问题是由unixODBC中的错误引起的,我在这里记录了它: 简单地说,unixODBC使用指针作为句柄,指向分配用于保存这些句柄数据的内存。由于句柄是32位整数,因此在64位体系结构上,它们被截断为最后一半(对于x86处理器) 因此,如果指针值小于2G,一切正常,但如果指

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


在主线程中进行相同的调用时,效果非常好。

我发现问题是由unixODBC中的错误引起的,我在这里记录了它:

简单地说,unixODBC使用指针作为句柄,指向分配用于保存这些句柄数据的内存。由于句柄是32位整数,因此在64位体系结构上,它们被截断为最后一半(对于x86处理器)

因此,如果指针值小于2G,一切正常,但如果指针值大于2G(而不是4G,因为符号位扩展),则unixODBC将无法找到句柄的数据结构,并将报告无效句柄

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

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

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

-- 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
--所有操作都是共享全局环境的ReaderT monad操作
--用于线程执行
--|等待辅助线程完成工作
waitForWorkToEnd::(MonadIO m)=>ReaderT MyEnvironment m()
waitForWorkToEnd=do
ThreadScontVar=检查。(>(return$return())(allocHandleChan>=\io->return(io>>waitIO))
liftIO$waitIO
liftIO$log$fromString$“所有工作线程都已完成”
--|在STM monad内创建IO操作,以在当前线程中分配新的处理程序
allocHandleT::(MonadIO m,MonadFail m)=>TQueue(SQLSMALLINT,SQLINTEGER,TMVar SQLINTEGER)->STM(m())
allocHandleT chan=do
(hType,hParent,retVar)>=liftIO。原子的。(putTMVar-retVar)
--|向主线程发出句柄alloc请求并等待结果
allocHandleReq::(MonadIO m,MonadFail m)=>SQLSMALLINT->SQLINTEGER->ReaderT MyEnvironment m SQLINTEGER
allocHandleReq htype hparent=do

allocHandleChan如果使用db连接或db池,可能需要更多连接句柄,后者在线程之间共享,如果使用单个连接,则必须为每个线程创建一个连接句柄thread@epsilonhalbe-我正在为每个线程使用不同的连接;我尝试为每个线程使用不同的环境;这似乎是64位平台上unixODBC中的一个bug;如果在主线程中分配句柄,那么它将工作;如果在与主线程不同的线程上分配句柄,那么它们将不工作,我猜这只会发生在64位平台上;我还在做一些研究,我不确定是不是很奇怪!如果你可以选择连接池,我相信它更经济。您是否检查了数据库允许多少开放连接的设置?(只是为了确保)同时我已经完成了我的研究,这确实是unixODBC中的一个bug。它以处理程序的形式返回用于处理程序后面的数据结构的指针的值。在32位系统中,这是一个32位整数,但在64位系统中,它被截断。因此,在我的例子中,例如对于值为0xc8000c50的处理程序,unixODBC在地址0x7f89c8000c50处分配了一个数据结构。在这个处理程序上调用函数时,它试图将其用作结构的地址,显然,它失败了。我认为这不是一个注释,而是一个答案!