{"id":4153,"date":"2013-06-06T07:00:00","date_gmt":"2013-06-06T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2013\/06\/06\/a-pathological-program-which-ignores-the-keyboard-and-understanding-the-resulting-behavior-based-on-what-we-know-about-the-synchronous-input\/"},"modified":"2013-06-06T07:00:00","modified_gmt":"2013-06-06T07:00:00","slug":"a-pathological-program-which-ignores-the-keyboard-and-understanding-the-resulting-behavior-based-on-what-we-know-about-the-synchronous-input","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20130606-00\/?p=4153","title":{"rendered":"A pathological program which ignores the keyboard, and understanding the resulting behavior based on what we know about the synchronous input"},"content":{"rendered":"<p>Today, we&#8217;ll illustrate the consequences of\n<A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2013\/06\/05\/10423678.aspx\">\nthe way the window manager synchronizes input<\/A>\nwhen two or more threads\ndecide to share an input queue.\n<\/P>\n<P>\nSince I need to keep separate state for the two windows,\nI&#8217;m going to start with the\n<A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2005\/04\/22\/410773.aspx\">\nnew scratch program<\/A>\nand\nmake the following changes:\n<\/P>\n<PRE>\n<FONT COLOR=\"blue\">#include &lt;strsafe.h&gt;<\/FONT><\/p>\n<p>class RootWindow : public Window\n{\npublic:\n virtual LPCTSTR ClassName() { return TEXT(&#8220;Scratch&#8221;); }\n static RootWindow *Create();<\/p>\n<p> <FONT COLOR=\"blue\">void AppendText(LPCTSTR psz)\n {\n  ListBox_SetCurSel(m_hwndChild,\n                    ListBox_AddString(m_hwndChild, psz));\n }<\/p>\n<p> void LogMessage(const MSG *pmsg)\n {\n   TCHAR szMsg[80];\n   StringCchPrintf(szMsg, 80, TEXT(&#8220;%d\\t%04x\\t%p\\t%p&#8221;),\n                   pmsg-&gt;time,\n                   pmsg-&gt;message,\n                   pmsg-&gt;wParam,\n                   pmsg-&gt;lParam);\n   AppendText(szMsg);\n}<\/FONT><\/p>\n<p>protected:\n LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);\n LRESULT OnCreate();\nprivate:\n HWND m_hwndChild;\n};<\/p>\n<p><FONT COLOR=\"blue\">LRESULT RootWindow::OnCreate()\n{\n m_hwndChild = CreateWindow(\n      TEXT(&#8220;listbox&#8221;), NULL,\n      LBS_HASSTRINGS | LBS_USETABSTOPS |\n      WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL,\n      0, 0, 0,0, GetHWND(), (HMENU)1, g_hinst, 0);<\/p>\n<p> return 0;\n}<\/FONT>\n<\/PRE>\n<P>\nAll we did above was add a list box to the window and provide\npublic methods\n<CODE>Append&shy;Text<\/CODE> to add a string to the list box\nand\n<CODE>Log&shy;Message<\/CODE> that adds a string based on the\ncontents of a <CODE>MSG<\/CODE> structure.\nWe&#8217;re going to use this list box to log what the program is doing.\n<\/P>\n<PRE>\n<FONT COLOR=\"blue\">bool ShouldLogMessage(UINT uMsg)\n{\n if (uMsg &gt;= WM_KEYFIRST &amp;&amp; uMsg &lt;= WM_KEYLAST) return true;\n if (uMsg &gt;= WM_MOUSEFIRST &amp;&amp; uMsg &lt;= WM_MOUSELAST) return true;\n return false;\n}<\/FONT>\n<\/PRE>\n<P>\nThis helper function above tells us which messages we want to log.\nFor now, let&#8217;s log keyboard and mouse messages.\n<\/P>\n<P>\nNow, in order to demonstrate input thread attachment,\nwe need two threads.\nHere comes the second thread:\n<\/P>\n<PRE>\n<FONT COLOR=\"blue\">DWORD CALLBACK AttachedThreadProc(void *lpParameter)\n{\n RootWindow *prw = RootWindow::Create();\n SetWindowText(prw-&gt;GetHWND(), TEXT(&#8220;Bad window&#8221;));\n AttachThreadInput(PtrToInt(lpParameter),\n                   GetCurrentThreadId(), TRUE);\n ShowWindow(prw-&gt;GetHWND(), SW_SHOW);<\/p>\n<p> BOOL fIgnoreKeyboard = FALSE;<\/p>\n<p> while (true) {\n  MSG msg;\n  BOOL fMessage;\n  if (fIgnoreKeyboard) {\n   fMessage =\n    PeekMessage(&amp;msg, NULL, 0, WM_KEYFIRST &#8211; 1, PM_REMOVE) ||\n    PeekMessage(&amp;msg, NULL, WM_KEYLAST + 1, 0xFFFFFFFF, PM_REMOVE);\n  } else {\n   fMessage = PeekMessage(&amp;msg, NULL, 0, 0, PM_REMOVE);\n  }<\/p>\n<p>  if (!fMessage) { WaitMessage(); continue; }<\/p>\n<p>  if (msg.message == WM_QUIT) break;<\/p>\n<p>  if (ShouldLogMessage(msg.message)) {\n   prw-&gt;LogMessage(&amp;msg);\n  }<\/p>\n<p>  if (msg.message == WM_KEYDOWN &amp;&amp; msg.wParam == VK_SHIFT) {\n   prw-&gt;AppendText(TEXT(&#8220;Stop processing keyboard messages&#8221;));\n   fIgnoreKeyboard = TRUE;\n  }<\/p>\n<p>  TranslateMessage(&amp;msg);\n  DispatchMessage(&amp;msg);\n }\n AttachThreadInput(PtrToInt(lpParameter),\n                   GetCurrentThreadId(), FALSE);\n return 0;\n}<\/FONT>\n<\/PRE>\n<P>\nThis second thread is intentionally ill-behaved,\nso that we can see what happens when there&#8217;s a bad apple in the barrel.\nThe thread processes messages normally,\nuntil you hit the shift key.\nOnce that happens, it goes into another mode where it starts\nignoring the keyboard by stubbornly refusing to pump any keyboard\nmessages.\n<\/P>\n<P>\nNormally, this sort of recalcitrant behavior would affect only\nthe thread itself,\nbut since this thread is attached to the main thread,\nthe scope of the damage expands.\n<\/P>\n<PRE>\nint PASCAL\nWinMain(HINSTANCE hinst, HINSTANCE, LPSTR, int nShowCmd)\n{\n g_hinst = hinst;<\/p>\n<p> if (SUCCEEDED(CoInitialize(NULL))) {\n  InitCommonControls();<\/p>\n<p>  RootWindow *prw = RootWindow::Create();\n  if (prw) {\n   ShowWindow(prw-&gt;GetHWND(), nShowCmd);<\/p>\n<p>  <FONT COLOR=\"blue\">DWORD dwId;\n  CreateThread(0, 0, AttachedThreadProc,\n               IntToPtr(GetCurrentThreadId()), 0, &amp;dwId);<\/FONT><\/p>\n<p>   MSG msg;\n   while (GetMessage(&amp;msg, NULL, 0, 0)) {<\/p>\n<p>   <FONT COLOR=\"blue\">if (ShouldLogMessage(msg.message)) {\n    prw-&gt;LogMessage(&amp;msg);\n   }<\/FONT><\/p>\n<p>    TranslateMessage(&amp;msg);\n    DispatchMessage(&amp;msg);\n   }\n  }\n  CoUninitialize();\n }\n return 0;\n}\n<\/PRE>\n<P>\nWe modify our main program to create the secondary thread\n(which attaches itself to the main thread),\nand then to log messages in its message pump.\n<\/P>\n<P>\nOkay, now run this program and use the mouse to resize and\nreposition the two windows side by side with no overlap.\n(This will make it easier to observe what&#8217;s going on.)\nWave the mouse over both windows, and click on each of the\nwindows and do some typing,\nbut don&#8217;t hit the shift key yet.\nSo far, everything works as you expect:\nFocus switches back and forth, mouse and keyboard messages are\ndelivered.\n<\/P>\n<P>\nNow put focus on the bad window and tap the shift key.\nThis puts the bad window into\n<CODE>fIgnore&shy;Keyboard = TRUE<\/CODE> mode,\nwhere it stops pumping keyboard messages\n(but pumps everything else).\n<\/P>\n<P>\nWhat we just did is leave the <CODE>WM_KEY&shy;UP<\/CODE> message\nfor the shift key in the input queue,\nand steadfastly refused to process it.\nThe message just sits there forever.\nLet&#8217;s see what this does to the input retrieval algorithm.\n<\/P>\n<P>\nWave the mouse over the bad window.\nNotice that mouse events are still delivered to the bad window.\n(Keyboard events are not delivered because the bad thread\nis not pumping keyboard messages.)\nThis makes sense, because the filtered <CODE>Peek&shy;Message<\/CODE>\nfor\n<CODE>WM_KEY&shy;LAST + 1<\/CODE> through\n<CODE>0xFFFFFFFF<\/CODE> includes the mouse message range\nbut excludes the keyboard message range,\nso the loop that looks for a candidate message completely\nignores the stuck keyboard message.\nAll it sees are mouse messages,\nand they are not stuck.\nThe code is taking advantage of the &#8220;peek into the future&#8221;\nfeature we mentioned yesterday.\n<\/P>\n<P>\nNext thing you notice is that if you wave the mouse over the\nmain window, it does <I>not<\/I> receive mouse input.\nThat&#8217;s because the main window performs an unfiltered peek.\nThe stuck keyboard message satisfies the filter,\nand since that message belongs to another thread\nand is ahead of all the mouse messages,\nthe input manager will not return the mouse messages\nuntil the stuck keyboard message is cleared out.\n<\/P>\n<P>\nThis also provides an example of the paradox I alluded to yesterday:\nThe main thread is not receiving any input because it is performing\nan unfiltered message retrieval,\nand there is a stuck keyboard message in the input queue.\nOn the other hand, if the main thread had explicitly peeked\nonly for mouse messages,\nthen the stuck keyboard message would not have been taken into\nconsideration, and it would have gotten the mouse messages.\nThe paradox is that under these strange conditions,\na filtered message retrieval actually returns messages\nthat an unfiltered retrieval would not!\n<\/P>\n<P>\nNow here&#8217;s another trick:\nClick on the main window.\n(Yes, it&#8217;s not processing mouse input, but do it anyway.)\nNow both windows stop responding to input.\nWhat happened?\n<\/P>\n<P>\nBack before you clicked on anything,\nthe only stuck input message was that keyboard message.\nSure, there were mouse motions that took place,\nbut we saw that\n<A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2011\/12\/19\/10249000.aspx\">\n<CODE>WM_MOUSE&shy;MOVE<\/CODE> messages are generated on demand<\/A>\nrather than being posted into the queue when the mouse moves.\nTherefore, all that mouse-waving didn&#8217;t actually leave a stuck\nmouse message in the queue.\nOn the other hand, when you click,\nthat generates a mouse click event in the queue,\nand those are generated when the click happens,\nnot on demand.\nTherefore, when you click on the main window,\na click event goes into the input queue.\n<\/P>\n<P>\nNow think about what&#8217;s in the input queue:\nThere is a stuck keyboard message (for the bad window,\nwhich is stuck because the bad window refuses to pump keyboard messages),\nand there is a stuck mouse message (for the main window,\nwhich is stuck because the main window is waiting for the stuck\nkeyboard message to clear out).\nNew keyboard input will not be processed because of the stuck\nkeyboard message,\nand\nnew mouse input will not be processed because of the stuck\nmouse message.\n<\/P>\n<P>\nResult: Nobody gets any input.\n<\/P>\n<P>\n<B>Bonus investigation<\/B>:\nWhile you&#8217;re in this horrible state,\nopen Task Manager.\nObserve that the scratch program has pegged a CPU.\nWhy is it draining CPU when there is nothing to do?\n<\/P>\n<P>\nThere&#8217;s a little extra step in the overall algorithm that\ndescribes how input is processed:\n<\/P>\n<UL>\n<LI>If the input queue is waiting for another thread to finish\n    processing an input message,\n    and the current thread is processing an inbound sent message,\n    then mark the input queue as no longer waiting.\n<LI>If the input queue is waiting for another thread to finish\n    processing an input message, then stop and return no message.\n<LI>If the input queue is waiting for the current thread to\n    finish processing an input message,\n    then mark the input queue as no longer waiting.\n<LI>Look at the first message in the input queue which\n    satisfies the message range filter and\n    either belongs to some other thread\n    or belongs to the current thread and matches the\n    window handle filter.\n    <UL>\n    <LI>If the message belongs to some other thread, then\n        <FONT COLOR=\"blue\">(New!) nudge the other thread to get it to\n        process the message<\/FONT>,\n        then stop. Return no message to the caller.\n    <LI>Otherwise,\n        mark the input queue as waiting for the current\n        thread to finish processing an input message,\n        and return the message we found.\n    <\/UL>\n<LI>If no such message exists,\n    then there is no input. Return no message.\n<\/UL>\n<P>\n<B>Reminder<\/B>: This is\n<A HREF=\"http:\/\/grammar.about.com\/od\/qaaboutrhetoric\/f\/QAmixmetaphor.htm\">\na peek under the hood at how the sausage is made<\/A>,\nand the algorithm described above is not contractual.\n<\/P>\n<P>\nIf the algorithm cannot return an input message because there\nis a stuck input message that belongs to another thread,\nthen the algorithm nudges that other thread\nby setting the appropriate queue state flag\n(for example,\n<CODE>QS_KEY<\/CODE> if it is a stuck keyboard message).\nIf the other thread is waiting for that type of message,\nthen the change in queue state will satisfy the wait,\nand the hope is that other thread will call\na message retrieval function to retrieve the stuck message\nand unclog the input queue.\n<\/P>\n<P>\nThat explains why the scratch program is pegging a processor.\nThe bad thread wants to peek out a mouse message,\nbut it can&#8217;t because of the stuck mouse click event that\nbelongs to the main thread,\nso it nudges the main thread to say,\n&#8220;Hey, I need you to process that mouse event.&#8221;\nThe main thread wakes up and tries to pump messages,\nbut it can&#8217;t retrieve any input because of the stuck\nkeyboard message.\nThe main thread therefore nudges the bad thread to say,\n&#8220;Hey, I need you to process that keyboard event.&#8221;\n<\/P>\n<P>\nThe two threads are therefore busy taking turns\nyelling at each other,\nsaying, &#8220;Hey, you, you need to get out of my way,&#8221;\nand together they burn a CPU.\n<\/P>\n<P>\nNow, this is admittedly a pathological program,\nbut it did do a pretty good job of highlighting some of the\nconsequences of synchronous input caused by attaching\nmultiple threads to the same input queue.\nThis is why it&#8217;s important that threads which share an input queue\nall be aware of the connection so that they don&#8217;t accidentally\ncause trouble for each other.\n<\/P>\n<P>\n<B>Exercise<\/B>:\nHow would you modify the above program to demonstrate the\n&#8220;waiting for a thread to finish processing a message&#8221; part\nof the input message retrieval algorithm?\n<\/P><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Today, we&#8217;ll illustrate the consequences of the way the window manager synchronizes input when two or more threads decide to share an input queue. Since I need to keep separate state for the two windows, I&#8217;m going to start with the new scratch program and make the following changes: #include &lt;strsafe.h&gt; class RootWindow : public [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-4153","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Today, we&#8217;ll illustrate the consequences of the way the window manager synchronizes input when two or more threads decide to share an input queue. Since I need to keep separate state for the two windows, I&#8217;m going to start with the new scratch program and make the following changes: #include &lt;strsafe.h&gt; class RootWindow : public [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/4153","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=4153"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/4153\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=4153"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=4153"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=4153"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}