Repository: pytorch/ignite

Issue:
TimeLimit handler for competitions
## 🚀 Feature

What do you think to be able to terminate a run after a time of computation ? This would be useful for using ignite on runtime environments like Colab or Kaggle which limit the time of session.

An implementation of such handler is available here and can be reused : ~https://github.com/Minyus/pipelinex/blob/master/src/pipelinex/ops/ignite/handlers/time_limit.py~
Updated link: https://github.com/Minyus/pipelinex/blob/master/src/pipelinex/extras/ops/ignite/handlers/time_limit.py

It misses documentation, tests, etc.


Discussion:
Your link seems to be broken. But I think I've already something like that in use. Not well formatted,  without help but perhaps a (more or less) good starting point. It measures time in hours.  I can implement it with tests but I have not much time at the moment. Could take me around 4 weeks :-|

```python
import time

from ignite.engine import Events


class TimeConstraint:
    def __init__(self, time_limit=0, time_formatter=lambda x: "{:.4f}h".format(x)):
        if not isinstance(time_limit, float) or not isinstance(time_limit, int):
            ValueError("time_limit must be of type float or int, got {} instead.".format(type(time_limit)))

        self._time_limit = time_limit
        self._time_formatter = time_formatter

    def _start_timer(self, engine):
        self._t0 = time.time()

    def _check_time_limit(self, engine):
        train_time = ((time.time() - self._t0) / 3600)
        if self._time_limit > 0 and (train_time > self._time_limit):
            engine.terminate()

            print("TimeConstraint: Training is terminated.")
            print("TimeConstraint: Maximum training time of {:} reached. Current training time is {}.".format(
                self._time_formatter(self._time_limit), self._time_formatter(train_time)))

    def attach(self, engine):
        if self._time_limit > 0:
            engine.add_event_handler(Events.STARTED, self._start_timer)
            engine.add_event_handler(Events.EPOCH_COMPLETED, self._check_time_limit)
```
Here is a correct link : https://github.com/Minyus/pipelinex/blob/ca64d78e34added4cd7ff19865aa724e71423394/src/pipelinex/extras/ops/ignite/handlers/time_limit.py

@Bibonaut yes, if you want to give a try on this, please go ahead. We have no deadline for this issue.
@vfdev-5 I would like to contribute.
> Here is a correct link : https://github.com/Minyus/pipelinex/blob/ca64d78e34added4cd7ff19865aa724e71423394/src/pipelinex/extras/ops/ignite/handlers/time_limit.py
> 
> @Bibonaut yes, if you want to give a try on this, please go ahead. We have no deadline for this issue.

I may be wrong. But I think the code given in link is already solving the issue without any modification.
@guptadhaval18 Thank you. 

The code is correct but it has to be modified because we expect an handler (see https://pytorch.org/ignite/concepts.html#events-and-handlers).

Documentation and tests are needed too.

Tell me if you want to be assigned on that issue.

HTH
@sdesrozis so basically 

- [x] the code has to be written in new file created at `ignite/handlers`
- [x] test has to be written at `tests/ignite/handlers`
- [x] docs has to be written in `docs/source/handlers.rst`

If yes, please assign me the task, else correct me if I am wrong.
Thanks for your help
@guptadhaval18 That is correct. Thank you for the help !

The code https://github.com/pytorch/ignite/issues/936#issuecomment-628019910 could help too.
@guptadhaval18 are you still working on the issue or I can unassign it ?
@vfdev-5  I understand the issue and I would like to work on it .
Sure, please go ahead @ahmedo42 , I can assign it to you.

Resolving patch:
diff --git a/ignite/handlers/__init__.py b/ignite/handlers/__init__.py
--- a/ignite/handlers/__init__.py
+++ b/ignite/handlers/__init__.py
@@ -5,6 +5,7 @@
 from ignite.handlers.checkpoint import Checkpoint, DiskSaver, ModelCheckpoint
 from ignite.handlers.early_stopping import EarlyStopping
 from ignite.handlers.terminate_on_nan import TerminateOnNan
+from ignite.handlers.time_limit import TimeLimit
 from ignite.handlers.timing import Timer
 
 __all__ = [
@@ -15,6 +16,7 @@
     "EarlyStopping",
     "TerminateOnNan",
     "global_step_from_engine",
+    "TimeLimit",
 ]
 
 
diff --git a/ignite/handlers/time_limit.py b/ignite/handlers/time_limit.py
new file mode 100644
--- /dev/null
+++ b/ignite/handlers/time_limit.py
@@ -0,0 +1,46 @@
+import logging
+import time
+from typing import Optional
+
+from ignite.engine import Engine
+
+__all__ = ["TimeLimit"]
+
+
+class TimeLimit:
+    """TimeLimit handler can be used to control training time for computing environments where session time is limited.
+    Timer starts when handler is created and not training started.
+    This handler gracefully terminates the training if time passed in the training exceeds a limit.
+
+    Args:
+        limit_sec (int, optional): Maximum time before training terminates (in seconds). Defaults to 28800.
+
+    Examples:
+
+        .. code-block:: python
+
+            from ignite.engine import Events
+            from ignite.handlers import TimeLimit
+
+            handler = TimeLimit() # 8 hours of training
+            trainer.add_event_handler(Events.ITERATION_COMPLETED, handler)
+
+    .. versionadded:: 0.4.3
+    """
+
+    def __init__(self, limit_sec: Optional[int] = 28800):
+
+        if not isinstance(limit_sec, int):
+            raise TypeError("Argument limit_sec should be an integer.")
+        if limit_sec <= 0:
+            raise ValueError("Argument limit_sec should be a positive integer.")
+
+        self.limit_sec = limit_sec
+        self.start_time = time.time()
+        self.logger = logging.getLogger(__name__ + "." + self.__class__.__name__)
+
+    def __call__(self, engine: Engine) -> None:
+        elapsed_time = time.time() - self.start_time
+        if elapsed_time > self.limit_sec:
+            self.logger.info("Reached the time limit: {} sec. Stop training".format(self.limit_sec))
+            engine.terminate()


Test patch:
diff --git a/tests/ignite/handlers/test_time_limit.py b/tests/ignite/handlers/test_time_limit.py
new file mode 100644
--- /dev/null
+++ b/tests/ignite/handlers/test_time_limit.py
@@ -0,0 +1,39 @@
+import time
+
+import pytest
+
+from ignite.engine import Engine, Events
+from ignite.handlers import TimeLimit
+
+
+def test_arg_validation():
+
+    with pytest.raises(ValueError, match=r"Argument limit_sec should be a positive integer."):
+        TimeLimit(limit_sec=-5)
+
+    with pytest.raises(TypeError, match=r"Argument limit_sec should be an integer."):
+        TimeLimit(limit_sec="abc")
+
+
+def test_terminate_on_time_limit():
+    def _train_func(engine, batch):
+        time.sleep(1)
+
+    def _test(n_iters, limit):
+        started = time.time()
+        trainer = Engine(_train_func)
+
+        @trainer.on(Events.TERMINATE)
+        def _():
+            trainer.state.is_terminated = True
+
+        trainer.add_event_handler(Events.ITERATION_COMPLETED, TimeLimit(limit))
+        trainer.state.is_terminated = False
+
+        trainer.run(range(n_iters))
+        elapsed = round(time.time() - started)
+        assert elapsed <= limit + 1
+        assert trainer.state.is_terminated == (n_iters > limit)
+
+    _test(20, 10)
+    _test(5, 10)