summaryrefslogtreecommitdiff
path: root/src/pal/tests/palsuite/threading/SleepEx/test2/test2.cpp
blob: c2ba4e704dcb67995246162dbe610501845b8fa9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

/*=====================================================================
**
** Source:  test2.c
**
** Purpose: Tests that a child thread in the middle of a SleepEx call will be
**          interrupted by QueueUserAPC if the alert flag was set.
**
**
**===================================================================*/

#include <palsuite.h>

const int ChildThreadSleepTime = 2000;
const int InterruptTime = 1000; 
/* We need to keep in mind that BSD has a timer resolution of 10ms, so
   we need to adjust our delta to keep that in mind. Besides we need some 
   tolerance to account for different scheduling strategies, heavy load 
   scenarios, etc.
   
   Real-world data also tells us we can expect a big difference between
   values when run on real iron vs run in a hypervisor.

   Thread-interruption times when run on bare metal will typically yield
   around 0ms on Linux and between 0 and 16ms on FreeBSD. However, when run
   in a hypervisor (like VMWare ESXi) we may get values around an order of
   magnitude higher, up to 110 ms for some tests.
*/
const DWORD AcceptableDelta = 150;

const int Iterations = 5;

void RunTest(BOOL AlertThread);
VOID PALAPI APCFunc(ULONG_PTR dwParam);
DWORD PALAPI SleeperProc(LPVOID lpParameter);

DWORD ThreadSleepDelta;

int __cdecl main( int argc, char **argv ) 
{
    int i;
    DWORD dwAvgDelta;

    if (0 != (PAL_Initialize(argc, argv)))
    {
        return FAIL;
    }

    /*
      On some platforms (e.g. FreeBSD 4.9) the first call to some synch objects 
      (such as conditions) involves some pthread internal initialization that 
      can make the first wait slighty longer, potentially going above the
      acceptable delta for this test. Let's add a dummy wait to preinitialize
      internal structures
    */
    Sleep(100);
  
    /* 
     * Check that Queueing an APC in the middle of a sleep does interrupt
     * it, if it's in an alertable state.
     */
    dwAvgDelta = 0;
    for (i=0;i<Iterations;i++)
    {
	RunTest(TRUE);
	dwAvgDelta += ThreadSleepDelta - InterruptTime;
    }
    dwAvgDelta /= Iterations;

    if (dwAvgDelta > AcceptableDelta)
    {
        Fail("Expected thread to sleep for %d ms (and get interrupted).\n"
             "Average delta: %u ms,  acceptable delta: %u\n", 
             InterruptTime, dwAvgDelta, AcceptableDelta);
    }

    /* 
     * Check that Queueing an APC in the middle of a sleep does NOT interrupt 
     * it, if it is not in an alertable state.
     */
    dwAvgDelta = 0;
    for (i=0;i<Iterations;i++)
    {
	RunTest(FALSE);
	dwAvgDelta += ThreadSleepDelta - ChildThreadSleepTime;
    }
    dwAvgDelta /= Iterations;

    if (dwAvgDelta > AcceptableDelta)
    {
        Fail("Expected thread to sleep for %d ms (and not be interrupted).\n"
             "Average delta: %u ms,  acceptable delta: %u\n", 
             ChildThreadSleepTime, dwAvgDelta, AcceptableDelta);
    }

    PAL_Terminate();
    return PASS;
}

void RunTest(BOOL AlertThread)
{
    HANDLE hThread = 0;
    DWORD dwThreadId = 0;
    int ret;

    hThread = CreateThread( NULL, 
                            0, 
                            (LPTHREAD_START_ROUTINE)SleeperProc,
                            (LPVOID) AlertThread,
                            0,
                            &dwThreadId);

    if (hThread == NULL)
    {
        Fail("ERROR: Was not able to create the thread to test SleepEx!\n"
            "GetLastError returned %d\n", GetLastError());
    }

    if (SleepEx(InterruptTime, FALSE) != 0)
    {
        Fail("The creating thread did not sleep!\n");
    }

    ret = QueueUserAPC(APCFunc, hThread, 0);
    if (ret == 0)
    {
        Fail("QueueUserAPC failed! GetLastError returned %d\n", GetLastError());
    }

    ret = WaitForSingleObject(hThread, INFINITE);
    if (ret == WAIT_FAILED)
    {
        Fail("Unable to wait on child thread!\nGetLastError returned %d.", 
            GetLastError());
    }
}

/* Function doesn't do anything, just needed to interrupt SleepEx */
VOID PALAPI APCFunc(ULONG_PTR dwParam)
{

}

/* Entry Point for child thread. */
DWORD PALAPI SleeperProc(LPVOID lpParameter)
{
    UINT64 OldTimeStamp;
    UINT64 NewTimeStamp;
    BOOL Alertable;
    DWORD ret;

    Alertable = (BOOL) lpParameter;

    LARGE_INTEGER performanceFrequency;
    if (!QueryPerformanceFrequency(&performanceFrequency))
    {
        return FAIL;
    }

    OldTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency);

    ret = SleepEx(ChildThreadSleepTime, Alertable);
    
    NewTimeStamp = GetHighPrecisionTimeStamp(performanceFrequency);

    if (Alertable && ret != WAIT_IO_COMPLETION)
    {
        Fail("Expected the interrupted sleep to return WAIT_IO_COMPLETION.\n"
            "Got %d\n", ret);
    }
    else if (!Alertable && ret != 0)
    {
        Fail("Sleep did not timeout.  Expected return of 0, got %d.\n", ret);
    }


    ThreadSleepDelta = NewTimeStamp - OldTimeStamp;

    return 0;
}