<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Morgan Ufuoma</title>
    <description>The latest articles on DEV Community by Morgan Ufuoma (@bluetheenigma).</description>
    <link>https://dev.to/bluetheenigma</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4163649%2Fcb4f6a21-4036-48ae-9dd4-e28d32218088.jpg</url>
      <title>DEV Community: Morgan Ufuoma</title>
      <link>https://dev.to/bluetheenigma</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bluetheenigma"/>
    <language>en</language>
    <item>
      <title>Poverty Inspired Me to Fix a 'Wine Can't Do This' Timeout</title>
      <dc:creator>Morgan Ufuoma</dc:creator>
      <pubDate>Tue, 06 Oct 2026 21:55:23 +0000</pubDate>
      <link>https://dev.to/bluetheenigma/poverty-inspired-me-to-fix-a-wine-cant-do-this-timeout-2p45</link>
      <guid>https://dev.to/bluetheenigma/poverty-inspired-me-to-fix-a-wine-cant-do-this-timeout-2p45</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A wise man once said, “if the young man is hungry, he should go to the forest and hunt the elephant. If he kills the elephant, the hunger ends, and if the elephant kills him, the hunger ends.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcgjke50sb23jceb0ka6e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcgjke50sb23jceb0ka6e.png" alt="Redacted Windows Terminal window showing the MT5 -10005 IPC timeout error" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My Monday poverty motivation led me to brave the waters of quantitative trading using algorithmic bots. As a Nigerian, familiar with unsteady power supply, I asked early: “how can I achieve 100% runtime even if I don’t get light for a week?” The answer led me to a VPS (Virtual Private Server).&lt;/p&gt;

&lt;p&gt;Ladies and gentlemen, tell me why the cheapest monthly subscription for a Windows OS is $50. My entire trading equity is $100, and I’m not even sure I have an edge yet. Scratch that. I’m sure I don’t currently have an edge.&lt;/p&gt;

&lt;p&gt;In search of the cheapest option I could get on the market, I stumbled on Linux on Contabo for $51/year.&lt;/p&gt;

&lt;p&gt;The problem: MetaTrader 5 is Windows-only, and I had to run it under Wine. And I had to run it Headlessly. Headlessly, in this case, simply meant it would run without a GUI. No pictures.&lt;/p&gt;

&lt;p&gt;To further complicate things, every forum thread said the same thing: don’t bother, MetaQuotes’ proprietary IPC layer can’t be emulated, use Windows Server or a Docker wrapper instead. Windows was already off the table, but what about Docker? My setup was an 8GB RAM, 75GB NVMe VPS. A proper multi-container Docker setup (three isolated broker instances, each with its own Wine runtime, virtual framebuffer, image layers, and logs) would have swallowed 20GB or more before I’d logged a single tick. On a 75GB disk, that’s not infrastructure, that’s a tax. Docker was out.&lt;/p&gt;

&lt;p&gt;I had to do the impossible. I had already paid for the VPS. There was no way I was wasting $51. I would actually cry.&lt;/p&gt;

&lt;p&gt;So I spent the last seven days trying to crack it, how to run MetaTrader 5 headlessly, and it almost cost me my head. I read as much as I could, gave up, went to AI, used every model available, and the only thing growing on my VPS was the number of orphaned processes. Until we went binary. Well, not me, technically. It was an agent that tunnelled into the VPS. But yes, we had to go binary to find where it was failing, and we finally found a fix.&lt;/p&gt;

&lt;p&gt;Okay. Jokes aside, here’s what actually happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;I was migrating a legacy multi-broker trading system off a fragile Windows-wrapper architecture and onto a hermetic, systemd-supervised Linux stack. Three isolated Wine prefixes, one per broker connection, each running its own MetaTrader 5 terminal alongside an embedded Windows Python instance that hosted a small bridge service. That bridge exposed MetaTrader5 to my native Linux Python stack over localhost TCP, since a Windows PE binary obviously can’t be imported straight into Linux Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  The challenge
&lt;/h2&gt;

&lt;p&gt;Before I even got to the real blocker, I had to fight through a full stack of Wine-on-Ubuntu-24.04 failures:&lt;/p&gt;

&lt;p&gt;A setup script that accidentally killed itself, because pkill -9 wine matched the word “wine” inside its own filename (setup_vps_wine_mt5.sh).&lt;/p&gt;

&lt;p&gt;winedbg popping a modal crash dialog on a headless server, silently hanging every unattended restart until I explicitly disabled it.&lt;/p&gt;

&lt;p&gt;A stale X11 lock file (/tmp/.X99-lock) breaking Xvfb on every crash recovery.&lt;/p&gt;

&lt;p&gt;MT5 refusing to boot past a “Windows 10 build is end-of-life” warning dialog, which I fixed by patching the Wine prefix’s registry to report a newer build number.&lt;/p&gt;

&lt;p&gt;A config file that silently failed to authenticate. Turned out cat &amp;lt;&amp;lt; EOF writes UTF-8 without a BOM, but MT5’s config loader needs UTF-16LE with a BOM, and bash/Wine’s CRLF handling was inserting duplicate carriage returns that corrupted every key.&lt;/p&gt;

&lt;p&gt;MT5 ignoring my Program Files config entirely once it had booted once, because it caches a copy in a hash-named roaming profile folder and reads from there instead.&lt;/p&gt;

&lt;p&gt;NumPy 2.2.2 crashing the whole Python process on import, because Wine’s ucrtbase.dll doesn’t implement a C99 intrinsic NumPy now depends on.&lt;/p&gt;

&lt;p&gt;Every one of these was individually solvable. None of them were the real problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boss fight
&lt;/h2&gt;

&lt;p&gt;Once all of that was cleared, I hit this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MetaTrader5&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mt5&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mt5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# ... 30 seconds of dead silence ...
&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mt5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last_error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10005&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;IPC timeout&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every resource I found agreed, and they all agreed on roughly the same theory. Wine’s Named Pipes were starved under load. The terminal’s message pump was choking behind some modal dialog. The whole thing was stuck in a kernel-level wait so deep not even Ctrl+C could reach it. It sounded sophisticated. It sounded plausible. It sounded like exactly the kind of low-level nonsense Wine would get wrong.&lt;/p&gt;

&lt;p&gt;I didn’t buy it. Not because I had a better theory, but because theories aren’t evidence, and nobody repeating this one had actually looked. If the terminal runs under Wine, and Python runs under Wine, there’s no physical reason two Windows processes in the same Wine prefix can’t talk to each other reliably. So instead of repeating forum wisdom, I decided to find out exactly what was happening on the wire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Opening the black box
&lt;/h2&gt;

&lt;p&gt;MetaTrader5’s Python package delegates everything to a compiled extension, _core.cp311-win_amd64.pyd. I used pefile to find its Import Address Table, then used ctypes to hook CreateFileW, WriteFile, and ReadFile in memory, redirecting the extension’s own Win32 calls through my own logging functions before they hit the real API.&lt;/p&gt;

&lt;p&gt;(The one genuinely nasty bug here: undeclared ctypes functions on 64-bit Windows default to a 32-bit signed return type, so an address above 0x7FFFFFFF gets sign-extended into a garbage negative pointer and crashes VirtualProtect. Declaring argtypes/restype explicitly on every hooked function fixed it.)&lt;/p&gt;

&lt;p&gt;With the hooks live, I ran mt5.initialize() again and watched the actual byte traffic for the first time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[WriteFile #1] 28 bytes - UTF-16LE string "Python" (client handshake)
[ReadFile]     12 bytes - build number reply (terminal handshake ack)
[WriteFile #2] 8 bytes  - opcode 0x6d (status query)
[ReadFile]     12 bytes - 6d 00 00 00 01 00 00 00 01 00 00 00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, every 100ms for 30 seconds straight: the same 0x6d query, the same reply. Wine wasn’t dropping a single packet. This was an internal state machine, stuck.&lt;/p&gt;

&lt;p&gt;So much for the starved pipe. So much for the choking message pump. The terminal wasn’t frozen. It was cheerfully replying “I’m still initializing” every single time, three hundred times in a row, while Python just sat there waiting for it to say something else.&lt;/p&gt;

&lt;p&gt;Somewhere around hour thirty I also tried the obvious thing. Ctrl+C, more than once, and watched it do absolutely nothing. Not a kernel-level trap, nothing mysterious. _core.pyd was holding the thread in a plain Sleep(100) loop inside compiled C code, never once yielding back to Python long enough for a signal to land. Python wasn’t ignoring me. It genuinely couldn’t hear me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dragon on the wall
&lt;/h2&gt;

&lt;p&gt;I disassembled the polling loop in _core.pyd with Capstone. The logic was simple: send opcode 0x6d, read a 12-byte reply, check byte 8 of that reply against a status code. 2 means ready, so return success. 3 means auth failed, so return an error. Otherwise, sleep 100ms and loop until a hardcoded timeout hits and it returns -10005.&lt;/p&gt;

&lt;p&gt;The terminal was stuck reporting status 1, “still initializing.” And that’s the deadlock: on a fresh headless boot, the terminal doesn’t consider itself fully synced until a symbol has been subscribed, but subscribing a symbol is something your Python code normally does after initialize() succeeds. The client waits on the terminal. The terminal waits on the client. Nobody moves.&lt;/p&gt;

&lt;h2&gt;
  
  
  The silver bullet
&lt;/h2&gt;

&lt;p&gt;Rather than patch the signed .pyd file on disk, I left the hook running permanently: whenever a 12-byte reply to opcode 0x6d came back reporting status 1, I rewrote that one byte to 2 before the extension’s own code ever saw it.&lt;/p&gt;

&lt;p&gt;Because _core.pyd only gets loaded into memory the moment MetaTrader5 is imported, the whole patch happens before mt5.initialize() is ever called. Locate the module, flip its Import Address Table to writable, swap in the hook, restore the original permissions. Nothing touched on disk, nothing persisted. Just memory, for the life of the process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;overlapped&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;real_ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;overlapped&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pipe_handles&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;r_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;read&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r_val&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c_uint8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;from_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mh"&gt;0x6d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x02&lt;/span&gt;  &lt;span class="c1"&gt;# force STATUS_INITIALIZING -&amp;gt; STATUS_READY
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;mt5.initialize() went from a guaranteed 30-second timeout to returning in 0.02 seconds. No forum thread I found had gotten this far. Most had simply given up and switched platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Here, my grimoire
&lt;/h2&gt;

&lt;p&gt;The system now runs three broker connections concurrently, each in its own isolated Wine prefix, each supervised by systemd with memory/CPU limits and automatic recovery, streaming live tick data into partitioned Parquet files for later analysis. Fully headless, fully automated, no Windows license anywhere in the stack.&lt;/p&gt;

&lt;p&gt;The patch, the diagnostic tool used to find it, and a working example of it wired into a real service are open on GitHub: &lt;a href="https://github.com/blueTheEnigma/wine-mt5-ipc-patch" rel="noopener noreferrer"&gt;https://github.com/blueTheEnigma/wine-mt5-ipc-patch&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>linux</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
