Thread: Re: [PATCHES] Win32 CHECK_FOR_INTERRUPTS() performance

Re: [PATCHES] Win32 CHECK_FOR_INTERRUPTS() performance

From
"Magnus Hagander"
Date:
> >>And the patch that was applied gives the same result.
> >>
> >>What is more, I am not seeing the reported speedup - in fact I am
> >>seeing no speedup worth mentioning.
> >>
> >>This is on XP-Pro, with default postgres settings. The test
> sets were
> >>somewhat larger than thos Magnus used - basically TPC-H
> lineitems and
> >>orders tables (6m and 1.5m rows respectively).
> >>
> >>
> >
> >First, that was Merlin and not me :-)
> >
> >
>
> My apologies to both of you. Or to at least one of you, anyway. :-)

:-)


> >Second, it didn't really show any improvement for him either in his
> >normal test. But when he re-ran it with just the count(*) test it
> >showed improvement. Did you run a count(*) test or some other test?
>
>
> The tests were
<snip>

That certainly looks like a similar test. Just checking :-)

You were on XP - Merlin, what OS were you on? It could be possible
things have chenged there as well...


> But it looks to me like we need to leave this for 8.2 now
> anyway, unless someone can quickly get to the bottom of why
> it's hanging.

Agreed. I definitly vote for backing out the patch so we don't block the
release. If we find the problem we put it back in before release, but if
it takes a while we just wait for 8.2.

//Magnus


Re: [PATCHES] Win32 CHECK_FOR_INTERRUPTS() performance

From
Tom Lane
Date:
"Magnus Hagander" <mha@sollentuna.net> writes:
> Agreed. I definitly vote for backing out the patch so we don't block the
> release. If we find the problem we put it back in before release, but if
> it takes a while we just wait for 8.2.

After digging in the Microsoft documentation a little bit, I think I
see the problem: we implement SIGALRM timeouts by means of a "waitable
timer", and the system's action when the timeout expires is:
       When a timer expires, the timer is set to the signaled state. If       the timer has a completion routine, it is
queuedto the thread       that set the timer. The completion routine remains in the APC       queue of the thread until
thethreads enters an alertable wait       state. At that time, the APC is dispatched and the completion       routine
iscalled.
 

In other words, since the main thread is the one that set the timer,
it has to run the completion routine (which just sets the signal flag).
The completion routine will never be run if the main thread is in a
tight loop with no kernel calls.  In particular, it was the frequent
execution of WaitForSingleObjectEx via CHECK_FOR_INTERRUPTS that allowed
this technique to work at all.

Isn't there some way we can get the timer completion routine to be run
by the signal thread instead?  This coding seems pretty unreliable to me
even without QQ's patch.
        regards, tom lane


Re: [PATCHES] Win32 CHECK_FOR_INTERRUPTS() performance

From
Tom Lane
Date:
I wrote:
> Isn't there some way we can get the timer completion routine to be run
> by the signal thread instead?  This coding seems pretty unreliable to me
> even without QQ's patch.

After further thought it seems like the right thing to do is to redesign
port/win32/timer.c so that it sets up a separate thread whose
responsibility is to wait for timeouts and deliver a SIGALRM signal back
to the main thread when they happen.  It's probably a bit late to
consider doing this for 8.1 :-(

I've temporarily disabled Qingqing's patch by the expedient of removing
the UNBLOCKED_SIGNAL_QUEUE() check in the macro, so that the out-of-line
routine is always called (since we're going to do a kernel call anyway,
one extra layer of subroutine call doesn't seem very important).  We can
put it back after fixing timer.c.
        regards, tom lane


Re: [PATCHES] Win32 CHECK_FOR_INTERRUPTS() performance

From
Andrew Dunstan
Date:

Tom Lane wrote:

>
>After further thought it seems like the right thing to do is to redesign
>port/win32/timer.c so that it sets up a separate thread whose
>responsibility is to wait for timeouts and deliver a SIGALRM signal back
>to the main thread when they happen.  It's probably a bit late to
>consider doing this for 8.1 :-(
>
>
>  
>

The hard part looks to be cancelling/changing the timer, which means 
that we can't just create a set and forget listener thread for a given 
timeout. Otherwise that seems to me the straightforward approach.

But maybe one of the Windows wizards has a better idea.

I doubt the changes would be very invasive - with luck just confined to 
timer.c.

cheers

andrew


Re: [PATCHES] Win32 CHECK_FOR_INTERRUPTS() performance

From
Tom Lane
Date:
Andrew Dunstan <andrew@dunslane.net> writes:
> The hard part looks to be cancelling/changing the timer, which means 
> that we can't just create a set and forget listener thread for a given 
> timeout. Otherwise that seems to me the straightforward approach.

Yeah.  I think probably the cleanest way is to create a persistent
thread that manages the timer.  We need a way for the main thread to
tell it to cancel the timer or change the setting.  Dunno enough about
Windows' interthread communication primitives to propose details.

> I doubt the changes would be very invasive - with luck just confined to 
> timer.c.

I don't see a need for anything else to know about it, either.
        regards, tom lane


Re: [PATCHES] Win32 CHECK_FOR_INTERRUPTS() performance

From
Qingqing Zhou
Date:
> Andrew Dunstan <andrew@dunslane.net> writes:
> > The hard part looks to be cancelling/changing the timer, which means
> > that we can't just create a set and forget listener thread for a given
> > timeout. Otherwise that seems to me the straightforward approach.
>
> Yeah.  I think probably the cleanest way is to create a persistent
> thread that manages the timer.  We need a way for the main thread to
> tell it to cancel the timer or change the setting.  Dunno enough about
> Windows' interthread communication primitives to propose details.
>

Oh my ... fortunately we got a timer test in regression.

I've come up with a quick patch implementing above discussions. Also,
seems by patching this, we can support setitimer(.,.,ovalue != NULL) --
because it is saved in the memory.

Regards,
Qingqing

---

Index: timer.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/port/win32/timer.c,v
retrieving revision 1.5
diff -u -r1.5 timer.c
--- timer.c    31 Dec 2004 22:00:37 -0000    1.5
+++ timer.c    23 Oct 2005 00:53:56 -0000
@@ -15,8 +15,16 @@
#include "libpq/pqsignal.h"

+/* Communication area of timer settings */
+typedef struct timerCA{
+    int which;
+    struct itimerval value;
+    HANDLE event;
+}timerCA;

+static timerCA timerCommArea;static HANDLE timerHandle = INVALID_HANDLE_VALUE;
+static HANDLE timerThreadHandle = INVALID_HANDLE_VALUE;
static VOID CALLBACKtimer_completion(LPVOID arg, DWORD timeLow, DWORD timeHigh)
@@ -28,16 +36,14 @@/* * Limitations of this implementation: *
- * - Does not support setting ovalue * - Does not support interval timer (value->it_interval) * - Only supports
ITIMER_REAL*/
 
-int
-setitimer(int which, const struct itimerval * value, struct itimerval * ovalue)
+static int
+do_setitimer(int which, const struct itimerval * value){    LARGE_INTEGER dueTime;

-    Assert(ovalue == NULL);    Assert(value != NULL);    Assert(value->it_interval.tv_sec == 0 &&
value->it_interval.tv_usec== 0);    Assert(which == ITIMER_REAL);
 
@@ -69,3 +75,56 @@
    return 0;}
+
+/* Timer ticking thread */
+static DWORD WINAPI
+pg_timer_thread(LPVOID param)
+{
+    Assert(param == NULL);
+
+    for (;;)
+    {
+        if (WaitForSingleObjectEx(timerCommArea.event, INFINITE, TRUE) == WAIT_OBJECT_0)
+        {
+            do_setitimer(timerCommArea.which, &timerCommArea.value);
+            ResetEvent(timerCommArea.event);
+        }
+    }
+
+    return 0;
+}
+
+/*
+ * Win32 setitimer emulation by creating a persistent thread
+ * to handle the timer setting and notification upon timeout.
+ */
+int
+setitimer(int which, const struct itimerval * value, struct itimerval * ovalue)
+{
+    Assert(value != NULL);
+
+    if (timerThreadHandle == INVALID_HANDLE_VALUE)
+    {
+        /* First call in this backend, create event and the timer thread */
+        timerCommArea.event = CreateEvent(NULL, TRUE, FALSE, NULL);
+        if (timerCommArea.event == NULL)
+            ereport(FATAL,
+                (errmsg_internal("failed to create timer event: %d", (int) GetLastError())));
+        MemSet(&timerCommArea.value, 0, sizeof(struct itimerval));
+
+        timerThreadHandle = CreateThread(NULL, 0, pg_timer_thread, NULL, 0, NULL);
+        if (timerThreadHandle == INVALID_HANDLE_VALUE)
+            ereport(FATAL,
+                (errmsg_internal("failed to create timer thread: %d", (int) GetLastError())));
+    }
+
+    /* Request the timer thread to change settings */
+    if (ovalue)
+        *ovalue = timerCommArea.value;
+    timerCommArea.which = which;
+    timerCommArea.value = *value;
+    SetEvent(timerCommArea.event);
+
+    /* Timer thread will handle possible errors */
+    return 0;
+}


Re: [PATCHES] Win32 CHECK_FOR_INTERRUPTS() performance

From
Andrew Dunstan
Date:
Well, first, have you tested it with "make check"? I am not sure if 
there's any great value in supporting a non null old value param.

Second, please note that the PostgreSQL community convention is for 
patches as context diffs, not unidiffs.

I guess there are several ways to skin this cat - the way I had sort of 
worked out reading the MSDN docs was to call QueueUserAPC on the timer 
thread. I'd like to know what Magnus and Merlin especially think out it.

cheers

andrew


Qingqing Zhou wrote:

>>Andrew Dunstan <andrew@dunslane.net> writes:
>>    
>>
>>>The hard part looks to be cancelling/changing the timer, which means
>>>that we can't just create a set and forget listener thread for a given
>>>timeout. Otherwise that seems to me the straightforward approach.
>>>      
>>>
>>Yeah.  I think probably the cleanest way is to create a persistent
>>thread that manages the timer.  We need a way for the main thread to
>>tell it to cancel the timer or change the setting.  Dunno enough about
>>Windows' interthread communication primitives to propose details.
>>
>>    
>>
>
>Oh my ... fortunately we got a timer test in regression.
>
>I've come up with a quick patch implementing above discussions. Also,
>seems by patching this, we can support setitimer(.,.,ovalue != NULL) --
>because it is saved in the memory.
>
>  
>


Re: [PATCHES] Win32 CHECK_FOR_INTERRUPTS() performance

From
Qingqing Zhou
Date:

On Sat, 22 Oct 2005, Andrew Dunstan wrote:

>
> Well, first, have you tested it with "make check"? I am not sure if
> there's any great value in supporting a non null old value param.
>

Yeah, I've managed to install in my slow windows box and tested it.
Supporting ovalue is just the by-product. If it works, I will complete the
patch by adding threading safe critical section protect.

> Second, please note that the PostgreSQL community convention is for
> patches as context diffs, not unidiffs.
>
Ok.

> I guess there are several ways to skin this cat - the way I had sort of
> worked out reading the MSDN docs was to call QueueUserAPC on the timer
> thread. I'd like to know what Magnus and Merlin especially think out it.
>
I am not sure - does this not require another thread in an alterable
state?

Regards,
Qingqing


Re: [PATCHES] Win32 CHECK_FOR_INTERRUPTS() performance

From
Qingqing Zhou
Date:
Here is the full patch of the timer implemenation with threading safty
added. Basic test is by several rounds of "make check"  and threading
safty test is by a SQL file with many lines of "set statement_timeout =
x". I don't know if there are any corner cases that I should consider, if
any, let me know.

Regards,
Qingqing

---

Index: timer.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/port/win32/timer.c,v
retrieving revision 1.5
diff -c -r1.5 timer.c
*** timer.c    31 Dec 2004 22:00:37 -0000    1.5
--- timer.c    23 Oct 2005 03:04:53 -0000
***************
*** 15,22 ****
--- 15,31 ----
 #include "libpq/pqsignal.h"

+ /* Communication area of timer settings */
+ typedef struct timerCA{
+     int which;
+     struct itimerval value;
+     HANDLE event;
+     CRITICAL_SECTION crit_sec;
+ }timerCA;

+ static timerCA timerCommArea; static HANDLE timerHandle = INVALID_HANDLE_VALUE;
+ static HANDLE timerThreadHandle = INVALID_HANDLE_VALUE;
 static VOID CALLBACK timer_completion(LPVOID arg, DWORD timeLow, DWORD timeHigh)
***************
*** 24,43 ****     pg_queue_signal(SIGALRM); }

- /*  * Limitations of this implementation:  *
-  * - Does not support setting ovalue  * - Does not support interval timer (value->it_interval)  * - Only supports
ITIMER_REAL */
 
! int
! setitimer(int which, const struct itimerval * value, struct itimerval * ovalue) {     LARGE_INTEGER dueTime;

-     Assert(ovalue == NULL);     Assert(value != NULL);     Assert(value->it_interval.tv_sec == 0 &&
value->it_interval.tv_usec== 0);     Assert(which == ITIMER_REAL);
 
--- 33,49 ----     pg_queue_signal(SIGALRM); }
 /*  * Limitations of this implementation:  *  * - Does not support interval timer (value->it_interval)  * - Only
supportsITIMER_REAL  */
 
! static int
! do_setitimer(int which, const struct itimerval * value) {     LARGE_INTEGER dueTime;
     Assert(value != NULL);     Assert(value->it_interval.tv_sec == 0 && value->it_interval.tv_usec == 0);
Assert(which== ITIMER_REAL);
 
***************
*** 69,71 ****
--- 75,136 ----
     return 0; }
+
+ /* Timer ticking thread */
+ static DWORD WINAPI
+ pg_timer_thread(LPVOID param)
+ {
+     Assert(param == NULL);
+
+     for (;;)
+     {
+         if (WaitForSingleObjectEx(timerCommArea.event, INFINITE, TRUE) == WAIT_OBJECT_0)
+         {
+             EnterCriticalSection(&timerCommArea.crit_sec);
+             do_setitimer(timerCommArea.which,
+                         &timerCommArea.value);
+             ResetEvent(timerCommArea.event);
+             LeaveCriticalSection(&timerCommArea.crit_sec);
+         }
+     }
+
+     return 0;
+ }
+
+ /*
+  * Win32 setitimer emulation by creating a persistent thread
+  * to handle the timer setting and notification upon timeout.
+  */
+ int
+ setitimer(int which, const struct itimerval * value, struct itimerval * ovalue)
+ {
+     Assert(value != NULL);
+
+     if (timerThreadHandle == INVALID_HANDLE_VALUE)
+     {
+         /* First call in this backend, create event and the timer thread */
+         timerCommArea.event = CreateEvent(NULL, TRUE, FALSE, NULL);
+         if (timerCommArea.event == NULL)
+             ereport(FATAL,
+                 (errmsg_internal("failed to create timer event: %d", (int) GetLastError())));
+         MemSet(&timerCommArea.value, 0, sizeof(struct itimerval));
+         InitializeCriticalSection(&timerCommArea.crit_sec);
+
+         timerThreadHandle = CreateThread(NULL, 0, pg_timer_thread, NULL, 0, NULL);
+         if (timerThreadHandle == INVALID_HANDLE_VALUE)
+             ereport(FATAL,
+                 (errmsg_internal("failed to create timer thread: %d", (int) GetLastError())));
+     }
+
+     /* Request the timer thread to change settings */
+     EnterCriticalSection(&timerCommArea.crit_sec);
+     if (ovalue)
+         *ovalue = timerCommArea.value;
+     timerCommArea.which = which;
+     timerCommArea.value = *value;
+     LeaveCriticalSection(&timerCommArea.crit_sec);
+     SetEvent(timerCommArea.event);
+
+     /* Timer thread will handle possible errors */
+     return 0;
+ }


Re: [PATCHES] Win32 CHECK_FOR_INTERRUPTS() performance

From
Andrew Dunstan
Date:

Qingqing Zhou wrote:

>>I guess there are several ways to skin this cat - the way I had sort of
>>worked out reading the MSDN docs was to call QueueUserAPC on the timer
>>thread. I'd like to know what Magnus and Merlin especially think out it.
>>
>>    
>>
>I am not sure - does this not require another thread in an alterable
>state?
>
>
>  
>

Maybe, I don't know. My impression from the docs was that the thread 
could call WaitForSingleObjectEx on the timer handle and it would also 
respond to the APC call. But my Windows API knowledge  is not exactly 
large. As long as what you have works it should be OK.

cheers

andrew