How to make thread sleep less than a millisecond on Windows
Asked Answered
W

19

65

On Windows I have a problem I never encountered on Unix. That is how to get a thread to sleep for less than one millisecond. On Unix you typically have a number of choices (sleep, usleep and nanosleep) to fit your needs. On Windows, however, there is only Sleep with millisecond granularity.

On Unix, I can use the use the select system call to create a microsecond sleep which is pretty straightforward:

int usleep(long usec)
{
    struct timeval tv;
    tv.tv_sec = usec/1000000L;
    tv.tv_usec = usec%1000000L;
    return select(0, 0, 0, 0, &tv);
}

How can I achieve the same on Windows?

Wahlstrom answered 17/9, 2008 at 16:37 Comment(5)
This does not work on Windows. The minimum "sleep" time with select is still approx 1 ms (Vista, RT thread, timeBeginPeriod(1), MMCSS "Pro Audio" RT Critical).Pinkeye
This is due to the fact that most machines Windows runs on have hardware limits in the 1-10ms range. PC Computer Hardware is cheap. You need to have dedicated hardware to keep accurate time. WiFi Cards for example: sub millisecond beacon timing must remain in hardware (even under Linux) due to the unreliability of a PC timekeeping.Nuclei
Linux can run on complex and embedded devices, which can provide for better granularity than most Windows PCs. The kernel itself, being open source, is also very customizable. Its scheduler can be made a near-real-time OS. Hence the need for nanosleep().Nuclei
Possible duplicate of Precise thread sleep needed. Max 1ms errorRouse
The NtDelayExecution API has units of 100 nanoseconds.Polypary
W
-5

On Windows the use of select forces you to include the Winsock library which has to be initialized like this in your application:

WORD wVersionRequested = MAKEWORD(1,0);
WSADATA wsaData;
WSAStartup(wVersionRequested, &wsaData);

And then the select won't allow you to be called without any socket so you have to do a little more to create a microsleep method:

int usleep(long usec)
{
    struct timeval tv;
    fd_set dummy;
    SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    FD_ZERO(&dummy);
    FD_SET(s, &dummy);
    tv.tv_sec = usec/1000000L;
    tv.tv_usec = usec%1000000L;
    return select(0, 0, 0, &dummy, &tv);
}

All these created usleep methods return zero when successful and non-zero for errors.

Wahlstrom answered 17/9, 2008 at 16:39 Comment(4)
This implantation of Sleep() leaks a socket every time it is called!Tori
This method sleeps for about 15 milliseconds even if only 1 microsecond sleep is requested.Anette
You don't need to create a socket to call select(). Please remove this answer.Sawyer
This copies the implementation, but not the semantics. You cannot implement a delay with a duration lower than the timer frequency. Sorry, this is useless, and highly misleading. At the very least, unselect this as the accepted answer. Of course, since you never decided to actually ask a question, any answer would be equally good, if it weren't ever so subtly misleading.Mascarenas
S
89

This indicates a mis-understanding of sleep functions. The parameter you pass is a minimum time for sleeping. There's no guarantee that the thread will wake up after exactly the time specified. In fact, threads don't "wake up" at all, but are rather chosen for execution by the OS scheduler. The scheduler might choose to wait much longer than the requested sleep duration to activate a thread, especially if another thread is still active at that moment.

Sanferd answered 17/9, 2008 at 16:40 Comment(5)
Yes, Sleep() just means a hint. According to MSDN the Sleep() time can actually be less than what you request. It's just a guide to the OS to improve performance, not really a good timing mechanism, at any granularity.Nuclei
However, the guarantee can be obtained by careful implementation. Proper setting of thread/process priorities and processor affinities are dicussed here.Goulette
There's serious misinformation here: "The parameter you pass is a minimum time for sleeping." Not true for windows: msdn.microsoft.com/en-gb/library/windows/desktop/… If dwMilliseconds is less than the resolution of the system clock, the thread may sleep for less than the specified length of time. If dwMilliseconds is greater than one tick but less than two, the wait can be anywhere between one and two ticks, and so on.Laryssa
Outstanding answer to a not existing question. Despite of the fact, that every answer to no question is correct, this one is really good.Philomenaphiloo
This simply doesn't answer the question. It's a good answer to a different question, so I hate to give you a downvote, but too many have wrongly upvoted you. Had you said "you can't, and here's why" you would have answered the question.Rheims
M
50

As Joel says, you can't meaningfully 'sleep' (i.e. relinquish your scheduled CPU) for such short periods. If you want to delay for some short time, then you need to spin, repeatedly checking a suitably high-resolution timer (e.g. the 'performance timer') and hoping that something of high priority doesn't pre-empt you anyway.

If you really care about accurate delays of such short times, you should not be using Windows.

Malloy answered 17/9, 2008 at 16:45 Comment(7)
-1 for not considering the user of the application may have different preferences then the developers.Sandler
@AustinMullins Can you elaborate on that point a bit? Were you looking for some statement about the evils of busy-spinning?Malloy
No, I was referring to the "you should not be using Windows" remark. The developer should make programs for the user, who probably wants the program to work in Windows.Sandler
Oh, I see. Well, if either the developer or his users foresee a hard real-time requirement as described in the question, then Windows is not the OS that either of them should be trying to use. That's neither a pejorative comment on Windows nor a licence for developer hubris, just an opinion on technical suitability, which (as a Windows developer :-) I'm happy to stand by.Malloy
I am one of the developers who is forced to use windows, because our app needs to cooperate with another one which is available also on Windows. After my tests, I agree with this solution, which provided the best results for us, but I highly disagree with your last sentence "you should not be using Windows": you cannot know why a developer has the need to sleep for a sub-ms interval. There's even a whole project about this hereMelessa
@Melessa - You can't reliably sleep (or delay in any other way) for such short times on Windows, (other than possibly by writing a kernel mode driver). It doesn't matter what other commercial or pragmatic requirements make Windows a good choice, it just isn't set up to do this sort of thing. As I have been saying on and off in the 10 years since I wrote the original answer, that's not a criticism, it's an observation. Your tight loop might wait for the correct time most of the time, but sometimes it will wait longer, and if you couldn't ever tolerate that, then you couldn't use Windows.Malloy
@WillDean But, using your solution (QueryPerformanceCounter), I have been able to sleep for microseconds on Windows, just like it's available on Unix. This is the original question. The question doesn't talk about RTOS, where an app couldn't ever tolerate a longer sleep. Even Sleep() can't guarantee that. And, even the Linux kernel on standard distributions doesn't guarantee that. I think the last sentence doesn't belong to this answer, even if I upvoted it.Melessa
S
37

Use the high resolution multimedia timers available in winmm.lib. See this for an example.

Sandblind answered 17/9, 2008 at 16:46 Comment(4)
I'm surprised this hasn't been upvoted more. It's a real solution. Why is everyone upvoting the "Sleep is only a guesstimate" and "stop using Windows, switch to Unix"? I admit that it's helpful to understand that Sleep is only a guesstimate, but that's not a solution. And not everyone has the option to leave Windows. To be clear, this solution only gets you to 1ms of precision (using the media library). That's what I need. I haven't tried the "select" technique (posted by Hendrick & smink), but it looks like that might get you down to sub-millisecond precision.Pediatrics
@GabeHalsmer linked code can only wait 1 or more mSec, that's not what's asked. Moreover it doesn't compile out of the box, is buggy and not thread-safe and might either cause deadlocks or not wait at all. Even after fixing some issues to make it do what it's supposed to do and ask it to wait 1 mSec I have seen it wait anywhere from 1 to 3mSec and those were just quick tests on a system without much load. As such it doesn't do any better, but worse, than the way simpler WaitableTimer/WaitForSingleObject combo which at least does do sub-mSec waits. So: not really a real solution for everyone.Professed
The Link is deadCopyreader
Here is an updated link that works in 2024: codeguru.com/windows/…Fomentation
L
26
#include <Windows.h>

static NTSTATUS(__stdcall *NtDelayExecution)(BOOL Alertable, PLARGE_INTEGER DelayInterval) = (NTSTATUS(__stdcall*)(BOOL, PLARGE_INTEGER)) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtDelayExecution");
static NTSTATUS(__stdcall *ZwSetTimerResolution)(IN ULONG RequestedResolution, IN BOOLEAN Set, OUT PULONG ActualResolution) = (NTSTATUS(__stdcall*)(ULONG, BOOLEAN, PULONG)) GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwSetTimerResolution");

static void SleepShort(float milliseconds) {
    static bool once = true;
    if (once) {
        ULONG actualResolution;
        ZwSetTimerResolution(1, true, &actualResolution);
        once = false;
    }

    LARGE_INTEGER interval;
    interval.QuadPart = -1 * (int)(milliseconds * 10000.0f);
    NtDelayExecution(false, &interval);
}

Works very well for sleeping extremely short times. Remember though that at a certain point the actual delays will never be consistent because the system can't maintain consistent delays of such a short time.

Lukewarm answered 14/7, 2015 at 15:57 Comment(3)
Fun fact: timeBeginPeriod/timeEndPeriod internally uses ZwSetTimerResolution and another fun fact is the minimum time resolution is 0.5ms, with timeBeginPeriod you will get minimum of 1ms but with ZwSetTimerResolution you can get 0.5ms, so calling ZwSetTimerResolution with 1 is an equivalent of calling it with 5000 and lower. (It's in 100ns unit AKA 10MHz limit)Debauch
will NtDelayExecution actually free CPU load and use it to do the job on other threads?Tigges
tested, NtDelayExecution frees CPU load.Tigges
S
12

Yes, you need to understand your OS' time quantums. On Windows, you won't even be getting 1ms resolution times unless you change the time quantum to 1ms. (Using for example timeBeginPeriod()/timeEndPeriod()) That still won't really guarantee anything. Even a little load or a single crappy device driver will throw everything off.

SetThreadPriority() helps, but is quite dangerous. Bad device drivers can still ruin you.

You need an ultra-controlled computing environment to make this ugly stuff work at all.

Swick answered 17/9, 2008 at 16:49 Comment(2)
You just have to be careful not to busy loop or it will starve other processes...Policlinic
I was mostly talking about other stuff you don't control here. It's fairly easy to have something that works on one computer totally fail on another because a bad device driver holds on too long. If you're designing something for internal use it's fine... if you need something you can release to the world this is really painful stuff.Swick
O
7

As several people have pointed out, sleep and other related functions are by default dependent on the "system tick". This is the minimum unit of time between OS tasks; the scheduler, for instance, will not run faster than this. Even with a realtime OS, the system tick is not usually less than 1 ms. While it is tunable, this has implications for the entire system, not just your sleep functionality, because your scheduler will be running more frequently, and potentially increasing the overhead of your OS (amount of time for the scheduler to run, vs. amount of time a task can run).

The solution to this is to use an external, high-speed clock device. Most Unix systems will allow you to specify to your timers and such a different clock to use, as opposed to the default system clock.

Oppenheimer answered 17/9, 2008 at 18:3 Comment(0)
G
7

Generally a sleep will last at least until the next system interrupt occurs. However, this depends on settings of the multimedia timer resources. It may be set to something close to 1 ms, some hardware even allows to run at interrupt periods of 0.9765625 (ActualResolution provided by NtQueryTimerResolution will show 0.9766 but that's actually wrong. They just can't put the correct number into the ActualResolution format. It's 0.9765625ms at 1024 interrupts per second).

There is one exception wich allows us to escape from the fact that it may be impossible to sleep for less than the interrupt period: It is the famous Sleep(0). This is a very powerful tool and it is not used as often as it should! It relinquishes the reminder of the thread's time slice. This way the thread will stop until the scheduler forces the thread to get cpu service again. Sleep(0) is an asynchronous service, the call will force the scheduler to react independent of an interrupt.

A second way is the use of a waitable object. A wait function like WaitForSingleObject() can wait for an event. In order to have a thread sleeping for any time, also times in the microsecond regime, the thread needs to setup some service thread which will generate an event at the desired delay. The "sleeping" thread will setup this thread and then pause at the wait function until the service thread will set the event signaled.

This way any thread can "sleep" or wait for any time. The service thread can be of big complexity and it may offer system wide services like timed events at microsecond resolution. However, microsecond resolution may force the service thread to spin on a high resolution time service for at most one interrupt period (~1ms). If care is taken, this can run very well, particulary on multi-processor or multi-core systems. A one ms spin does not hurt considerably on multi-core system, when the affinity mask for the calling thread and the service thread are carefully handled.

Code, description, and testing can be visited at the Windows Timestamp Project

Goulette answered 12/7, 2012 at 16:12 Comment(0)
A
5

What are you waiting for that requires such precision? In general if you need to specify that level of precision (e.g. because of a dependency on some external hardware) you are on the wrong platform and should look at a real time OS.

Otherwise you should be considering if there is an event you can synchronize on, or in the worse case just busy wait the CPU and use the high performance counter API to measure the elapsed time.

Attic answered 17/9, 2008 at 16:47 Comment(0)
L
5

If you want so much granularity you are in the wrong place (in user space).

Remember that if you are in user space your time is not always precise.

The scheduler can start your thread (or app), and schedule it, so you are depending by the OS scheduler.

If you are looking for something precise you have to go: 1) In kernel space (like drivers) 2) Choose an RTOS.

Anyway if you are looking for some granularity (but remember the problem with user space ) look to QueryPerformanceCounter Function and QueryPerformanceFrequency function in MSDN.

Leu answered 17/9, 2008 at 18:3 Comment(0)
M
3

Actually using this usleep function will cause a big memory/resource leak. (depending how often called)

use this corrected version (sorry can't edit?)

bool usleep(unsigned long usec)
{
    struct timeval tv;
    fd_set dummy;
    SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    FD_ZERO(&dummy);
    FD_SET(s, &dummy);
    tv.tv_sec = usec / 1000000ul;
    tv.tv_usec = usec % 1000000ul;
    bool success = (0 == select(0, 0, 0, &dummy, &tv));
    closesocket(s);
    return success;
}
Manganous answered 19/9, 2009 at 16:34 Comment(2)
You do not need to create a socket just to be able to call select()Generous
I fixed the code sample. Be aware that you have to call WSAStartup/WSACleanup before/after using this function when coding under Windows.Male
C
2

I have the same problem and nothing seems to be faster than a ms, even the Sleep(0). My problem is the communication between a client and a server application where I use the _InterlockedExchange function to test and set a bit and then I Sleep(0).

I really need to perform thousands of operations per second this way and it doesn't work as fast as I planned.

Since I have a thin client dealing with the user, which in turn invokes an agent which then talks to a thread, I will move soon to merge the thread with the agent so that no event interface will be required.

Just to give you guys an idea how slow this Sleep is, I ran a test for 10 seconds performing an empty loop (getting something like 18,000,000 loops) whereas with the event in place I only got 180,000 loops. That is, 100 times slower!

Catbird answered 13/5, 2009 at 20:2 Comment(1)
Does not surprise me. As the empty loop will be running in the cpu internal cache when its empty. It wouldn't surprise me if the compiler even optimized the loop and just gave you the result. A better test wold actually doing something on both cases and the compare the result. try doing an _InterlockedIncrement (the intrinsic) on each interaction of both loops.Brim
L
2

Try using SetWaitableTimer...

Lowelllowenstein answered 3/7, 2015 at 6:49 Comment(0)
M
2

If your goal is to "wait for a very short amount of time" because you are doing a spinwait, then there are increasing levels of waiting you can perform.

void SpinOnce(int& spin)
{
   /*
      SpinOnce is called each time we need to wait. 
      But the action it takes depends on how many times we've been spinning:

      1..12 spins: spin 2..4096 cycles
      12..32: call SwitchToThread (allow another thread ready to go on time core to execute)
      over 32 spins: Sleep(0) (give up the remainder of our timeslice to any other thread ready to run, also allows APC and I/O callbacks)
   */
   spin += 1;
  
   if (spin > 32)
      Sleep(0); //give up the remainder of our timeslice
   else if (spin > 12)
      SwitchToThread(); //allow another thread on our CPU to have the remainder of our timeslice
   else
   {
      int loops = (1 << spin); //1..12 ==> 2..4096
      while (loops > 0)
         loops -= 1;
   }
}

So if your goal is actually to wait only for a little bit, you can use something like:

int spin = 0;
while (!TryAcquireLock()) 
{ 
   SpinOne(spin);
}

The virtue here is that we wait longer each time, eventually going completely to sleep.

Mawkish answered 22/4, 2017 at 21:18 Comment(0)
D
1

Like everybody mentioned, there is indeed no guarantees about the sleep time. But nobody wants to admit that sometimes, on an idle system, the usleep command can be very precise. Especially with a tickless kernel. Windows Vista has it and Linux has it since 2.6.16.

Tickless kernels exists to help improve laptops batterly life: c.f. Intel's powertop utility.

In that condition, I happend to have measured the Linux usleep command that respected the requested sleep time very closely, down to half a dozen of micro seconds.

So, maybe the OP wants something that will roughly work most of the time on an idling system, and be able to ask for micro second scheduling! I actually would want that on Windows too.

Also Sleep(0) sounds like boost::thread::yield(), which terminology is clearer.

I wonder if Boost-timed locks have a better precision. Because then you could just lock on a mutex that nobody ever releases, and when the timeout is reached, continue on... Timeouts are set with boost::system_time + boost::milliseconds & cie (xtime is deprecated).

Debra answered 11/5, 2012 at 15:50 Comment(0)
B
1

There are different approached how to sleep precise number of milliseconds on windows. However, there is nothing that allows microsecond precision. In general, precision of Sleep function is around 16 ms (default system clock is usually 64Hz). Functions like timeBeginPeriod or the undocumented ZwSetTimerResolution could be used to increase the resolution to 0.5-1ms. Busy waiting can be used to sleep with microsecond resolution. These approached can be combined to get usleep implementation with microsecond precision of the usleep implementation:

#include <windows.h>
#include <thread>

void usleep(long long usec)
{
    if (usec <= 0)
    {
        SwitchToThread();
        return;
    }
    const int busywait = 2000; // Up to last busywait/10 usec are busy waited

    LARGE_INTEGER t0, freq;
    QueryPerformanceCounter(&t0);
    QueryPerformanceFrequency(&freq);
    if (usec - busywait > 0)
    {
        static bool resolutionSet = false;
        if (!resolutionSet)
        {
            HMODULE ntdll = GetModuleHandleA("ntdll.dll");
            if (ntdll)
            {
                auto ZwSetTimerResolution = (LONG(__stdcall*)(ULONG, BOOLEAN, PULONG))
                    GetProcAddress(ntdll, "ZwSetTimerResolution");
                if (ZwSetTimerResolution)
                {
                    ULONG actualResolution;
                    ZwSetTimerResolution(1, true, &actualResolution);
                }
            }
            resolutionSet = true;
        }
        std::this_thread::sleep_for(std::chrono::microseconds(usec - busywait));
    }
    for (;;) // busy wait the remainder
    {
        LARGE_INTEGER t1;
        QueryPerformanceCounter(&t1);
        long long waited = ((t1.QuadPart - t0.QuadPart) / freq.QuadPart) * 1000000LL +
            ((t1.QuadPart - t0.QuadPart) % freq.QuadPart) * 1000000LL / freq.QuadPart;
        if (waited >= usec)
            break;
        if (usec - waited > busywait / 10) // full busy wait last 200 usec
            SwitchToThread();
    }
}

It uses std::this_thread::sleep_for to sleep. Any other suitable sleep function could be used, as they all have approximately the same characteristics. Up to last 2ms this function does manual waiting based on qpc time. It does full busy wait only last 200 microseconds.

Here's sample code to test precision:

#include <windows.h>
#include <stdlib.h>
#include <time.h>

void usleep(long long usec);

int main()
{
    srand(time(NULL));
    long long totaldiff = 0;
    const int NN = 1000;
    for (int i = 0; i < NN; ++i)
    {
        int r = 1 + rand() % 10000;
        LARGE_INTEGER t0, t1;
        QueryPerformanceCounter(&t0);

        usleep(r);

        QueryPerformanceCounter(&t1);
        totaldiff += (t1.QuadPart - t0.QuadPart) - r*10LL; // assume usual 10MHz qpc clock
    }
    printf("average over wait is: %lld/10 usec", totaldiff / NN);
}

On my laptop most of the time I'm getting sub-microsecond precision.

Biometry answered 5/2 at 14:18 Comment(0)
J
0

Just use Sleep(0). 0 is clearly less than a millisecond. Now, that sounds funny, but I'm serious. Sleep(0) tells Windows that you don't have anything to do right now, but that you do want to be reconsidered as soon as the scheduler runs again. And since obviously the thread can't be scheduled to run before the scheduler itself runs, this is the shortest delay possible.

Note that you can pass in a microsecond number to your usleep, but so does void usleep(__int64 t) { Sleep(t/1000); } - no guarantees to actually sleeping that period.

Julee answered 18/9, 2008 at 8:59 Comment(3)
I think that if you actually try this, you'll find that Sleep(0) usually sleeps for 10-15ms depending on your hardware and system load.Calumny
From MSDN: A value of zero causes the thread to relinquish the remainder of its time slice to any other thread of equal priority that is ready to run. If there are no other threads of equal priority ready to run, the function returns immediately, and the thread continues execution.Pentomic
The last part of the answer is wrong. If you divide a int by 1000, then any value below 1000 will be set to 0. So your usleep function will simply call Sleep(0) for any value below a millisecond. And, as @Pentomic said, Sleep(0) does not sleep for less than a millisecond, it's jeopardy here.Doit
M
0

Sleep function that is way less than a millisecond-maybe

I found that sleep(0) worked for me. On a system with a near 0% load on the cpu in task manager, I wrote a simple console program and the sleep(0) function slept for a consistent 1-3 microseconds, which is way less than a millisecond.

But from the above answers in this thread, I know that the amount sleep(0) sleeps can vary much more wildly than this on systems with a large cpu load.

But as I understand it, the sleep function should not be used as a timer. It should be used to make the program use the least percentage of the cpu as possible and execute as frequently as possible. For my purposes, such as moving a projectile across the screen in a videogame much faster than one pixel a millisecond, sleep(0) works, I think.

You would just make sure the sleep interval is way smaller than the largest amount of time it would sleep. You don't use the sleep as a timer but just to make the game use the minimum amount of cpu percentage possible. You would use a separate function that has nothing to do is sleep to get to know when a particular amount of time has passed and then move the projectile one pixel across the screen-at a time of say 1/10th of a millisecond or 100 microseconds.

The pseudo-code would go something like this.

while (timer1 < 100 microseconds) {
sleep(0);
}

if (timer2 >=100 microseconds) {
move projectile one pixel
}

//Rest of code in iteration here

I know the answer may not work for advanced issues or programs but may work for some or many programs.

Midi answered 31/10, 2015 at 5:33 Comment(2)
In games you should not rely on frame timings, instead use speed * timepassed. This way you will not have any problems if the PC is overloaded.Proteus
@CemKalyoncu = or if the same code is run much later on considerably better hardware. I wrote some old roto-zoomers, particle systems and assorted effects on a 386 and 486.I'm sure you can imagine how obscene they were when run on an i3 (in one case, about 20 frames a second to well over 700!!!)Doyle
H
0

If the machine is running Windows 10 version 1803 or later then you can use CreateWaitableTimerExW with the CREATE_WAITABLE_TIMER_HIGH_RESOLUTION flag.

Herson answered 30/6, 2022 at 23:57 Comment(0)
W
-5

On Windows the use of select forces you to include the Winsock library which has to be initialized like this in your application:

WORD wVersionRequested = MAKEWORD(1,0);
WSADATA wsaData;
WSAStartup(wVersionRequested, &wsaData);

And then the select won't allow you to be called without any socket so you have to do a little more to create a microsleep method:

int usleep(long usec)
{
    struct timeval tv;
    fd_set dummy;
    SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    FD_ZERO(&dummy);
    FD_SET(s, &dummy);
    tv.tv_sec = usec/1000000L;
    tv.tv_usec = usec%1000000L;
    return select(0, 0, 0, &dummy, &tv);
}

All these created usleep methods return zero when successful and non-zero for errors.

Wahlstrom answered 17/9, 2008 at 16:39 Comment(4)
This implantation of Sleep() leaks a socket every time it is called!Tori
This method sleeps for about 15 milliseconds even if only 1 microsecond sleep is requested.Anette
You don't need to create a socket to call select(). Please remove this answer.Sawyer
This copies the implementation, but not the semantics. You cannot implement a delay with a duration lower than the timer frequency. Sorry, this is useless, and highly misleading. At the very least, unselect this as the accepted answer. Of course, since you never decided to actually ask a question, any answer would be equally good, if it weren't ever so subtly misleading.Mascarenas

© 2022 - 2024 — McMap. All rights reserved.