How do online judges identify Time Limit Exceeded?

Question:I have to design C++ code that calls a function. But I must not run that function for more than 2 seconds.

Answer

Slow down young man, and let's proceed steadily. Shall we?

Now what exactly you want to achieve is unclear here. Do you simply want to measure the execution time of the function? Do you want to terminate the program after the time limit is exceeded? Are you calling the function in a child process? Is your process running as a privileged user?The implementation obviously depends on what you want to do and what environments you are initializing the process with.

Nevertheless, there are few different ways. I will get to them one by one with simplest first.

1. Measuring time

You can easily measure the time (clock time & CPU time) taken to execute a function using clock() and time() functions available in the header <time.h>.

See the code below to understand how you can check whether a function takes more time than you expect it. There are comments in between the code to make it easier to understand.

You can test by commenting `sleep(3)' and uncommenting `sleep(1)' in my_function() to see what is happening. (Although mixing alarm() and sleep() is a very bad idea, I thought this is the easiest way to show you the effect.)

Now what happens is that when my_function() is still executing and TIME_LIMIT is crossed, SIGALRM is fired and alarm_handler() is invoked. It then prints the message "Seems you crossed time limit!". You can even kill the process using kill() in alarm_handler.

3. Resource Limits

Online judges don't implement TLE by the nasty method I have mentioned above. They use `struct rlimit' to enforce any type of resource limit including the time limit. Besides that, there are lots of other signals and checks used for security and integrity of online judges and even the simple implementations run into thousands of lines of code.

Before proceeding forward, you should take a look at 'man setrlimit'.

In C programs, you can set or get resource limits by setrlimit and getrlimit respectively. But the caveat is that only a privileged process may make arbitrary changes to either limit value.

Each resource has an associated soft and hard limit, as defined by the rlimit structure.

1
2
3
4

struct rlimit {

rlim_t rlim_cur; /* Soft limit */

rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) */

};

The code snippet below shows how it can be implemented in a very rudimentary way, but enough to give an idea.

-- snippet --

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

#include <signal.h>

#include <sys/time.h>

#include <sys/resource.h>

#define TIME_LIMIT 2

if (sigaction(SIGALRM, NULL, NULL) == -1) {

return -1;

} else if (sigaction(SIGXCPU, NULL, NULL) == -1) {

return -1;

}

struct rlimit rlim;

rlim.rlim_cur = rlim.rlim_max = TIME_LIMIT

if (setrlimit(RLIMIT_CPU, &rlim) < 0)

err(1, NULL);

-- /snippet --

If your program is running as privileged (root) user, setrlimit sets the CPU time limit for the program to TIME_LIMIT as defined in the code. When the soft time limit (rlim_cur) is crossed, SIGXCPU is sent to the process and when the hard time limit (rlim_max) is crossed, SIGKILL is sent to the process. You can catch SIGXCPU and do a clean exit.

Online judges

The implementation of online judges often takes into account many other factors like memory limit and security which makes them more complicated. But the underlying mechanism remains the same. Step by step they can be written as:

Run the process as privileged user

Install all signal handlers

duplicate input file stream to stdin

create a child process

set resource limit values in child process

switch to unprivileged user in child process

exec the program to run in child process

In parent process, check for the signals sent using WIFSIGNALED and WTERMSIG, etc. and determine the state of child process.