def test_import_fail_windows() -> None:
    """
    Expects
    -------
    * Should fail to import if the memory limit is too low

    Note
    ----
    For sklearn on windows, this triggers an OSError with code 22 and winerr 1455
    and so we have to be aware of it before hand.
    There's no automatic way to identfiy this from a regular OSError so we better
    be explicit about it.
    """
    m = Monitor()
    print("Before job ", m.memory("mb"))
    try:
        with limit(
                import_sklearn,
                memory=(100, "mb"),
                wrap_errors={"memory": [(OSError, 22, 1455)]},
        ) as lf:
            lf()
    except MemoryLimitException:
        pass
    except Exception as e:
        print(e, type(e))
        raise e
def import_sklearn() -> bool:
    """Just imports sklearn"""
    m = Monitor()
    print("Before import ", m.memory("mb"))

    import sklearn  # noqa

    print("After import ", m.memory("mb"))

    return True
def train_svr(model: Any, X: np.ndarray, y: np.ndarray, limit: Any) -> bool:
    """Trains an svr model"""
    m = Monitor()
    print("Memory before fit", m.memory("mb"))
    print("Limit ", limit)

    # This will cause a coredump with sigdev
    with open(os.devnull, "rb") as f:
        faulthandler.enable(file=f)
        model.fit(X, y)
    print("Memory after fit", m.memory("mb"))

    return True
def test_import_fail_all() -> None:
    """
    Expects
    -------
    * Should fail to import but give a PynisherException as we can't properly
      identify that it's root cause is due to memory
    """
    m = Monitor()
    print("Before job ", m.memory("mb"))
    try:
        with limit(import_sklearn, wrap_errors=True, memory=(100, "mb")) as lf:
            lf()
    except MemoryLimitException as e:
        raise e
    except PynisherException:
        pass
    except Exception as e:
        print(e, type(e))
        raise e
def test_train_svr_memory(context: str) -> None:
    """
    Expects
    -------
    * Should raise a MemoryError if it ran out of memory during fit

    Note
    ----
    What will actually happen is a segmentation fault, we deal with this
    in `pynisher`
    """
    m = Monitor()
    from sklearn.datasets import make_regression
    from sklearn.svm import SVR

    model = SVR()
    X, y = make_regression(n_samples=30_000, n_features=128)

    # Seem fit will consume about 28mb extra, see __main__
    # Add 1MB
    too_little_mem = round(m.memory("MB") + 1)

    lf = limit(train_svr, memory=(too_little_mem, "MB"))

    completed = False
    with pytest.raises(MemoryLimitException):
        completed = lf(model, X, y, too_little_mem)

    assert completed is not True
    assert lf._process is not None and not lf._process.is_running()

    try:
        children = lf._process.children()
        assert not any(children)
    except psutil.NoSuchProcess:
        pass
Exemple #6
0
def test_success(limit: int, context: str) -> None:
    """Using less than the allocated memory should be fine

    Processes take up some amount of memory natively, e.g. 37MB was preallocated
    for my own run on Linx where "fork" is used.

    Hence, we skip if the test limit is not enough
    """
    allocate = int(limit / 3)

    current_usage = Monitor().memory("MB")
    expected_usage = current_usage + allocate

    if expected_usage > limit:
        msg = (
            f"Limit {limit}MB is too low as the current usage is {current_usage}MB"
            f" with another {allocate}MB being allocated. This will total "
            f" {expected_usage}MB, over the limit.")
        pytest.skip(msg)

    rf = Pynisher(usememory, memory=(limit, "MB"), context=context)

    rf((allocate, "MB"))
        children = lf._process.children()
        assert not any(children)
    except psutil.NoSuchProcess:
        pass


if __name__ == "__main__":
    """
    For checking memory usage with the SVM

    My machine:
    Before model and data 689.390625
    After model and data 836.6640625
    After fit 864.01953125
    Fitting duration in cputime  89.290726962

    It seems it takes about 28mb extra to fit with (30_000, 128) data
    """
    from sklearn.datasets import make_regression
    from sklearn.svm import SVR

    m = Monitor()

    print("Before model and data", m.memory("mb"))
    X, y = make_regression(n_samples=30_000, n_features=128)
    model = SVR()
    print("After model and data", m.memory("mb"))

    model.fit(X, y)
    print("After fit", m.memory("mb"))
Exemple #8
0
    def __call__(self, *args: Any, **kwargs: Any) -> None:
        """Set process limits and then call the function with the given arguments.

        Note
        ----
        This should usually not be called by the end user, it's main use is in
        Pynisher::run().

        Parameters
        ----------
        *args, **kwargs
            Arguments to the function

        Returns
        -------
        None
        """
        try:

            if self.cpu_time is not None:
                self.limit_cpu_time(self.cpu_time)

            if self.memory is not None:
                # We should probably warn if we exceed the memory usage before
                # any limitation is set
                memusage = Monitor().memory("B")
                if self.memory <= memusage:
                    self._raise_warning(
                        f"Current memory usage in new process is {memusage}B but "
                        f" setting limit to {self.memory}B. Likely to fail, try"
                        f" increasing the memory limit"
                    )
                self.limit_memory(self.memory)

            # Call our function
            result = self.func(*args, **kwargs)
            error = None
            tb = None

        except Exception as e:
            result = None
            error = e
            tb = "".join(traceback.format_exception(*sys.exc_info()))

        if error is not None:
            error = self._wrap_error(error, *args, **kwargs)

        # Now let's try to send the result back
        response = (result, error, tb)
        try:
            self.output.send(response)
            self.output.close()
            if self.terminate_child_processes is True:
                terminate_process(timeout=2, children=True, parent=False)
            return
        except Exception as e:
            failed_send_tb = "".join(traceback.format_exception(*sys.exc_info()))
            failed_send_response = (None, e, failed_send_tb)

        # We failed to send the result back, lets try to send the errors we got from
        # doing so
        try:
            self.output.send(failed_send_response)
            self.output.close()
            if self.terminate_child_processes is True:
                terminate_process(timeout=2, children=True, parent=False)
            return
        except Exception:
            pass

        # One last effot to send the smallest None through
        last_response_attempt = None
        try:
            self.output.send(last_response_attempt)
            self.output.close()
            if self.terminate_child_processes is True:
                terminate_process(timeout=2, children=True, parent=False)
            return
        except Exception:
            pass

        # Look, everything failed, try kill any child processes and just kill this
        # process is we can't for memory reasons
        self.output.close()
        try:
            if self.terminate_child_processes is True:
                terminate_process(timeout=2, children=True, parent=False)
        except MemoryError:
            os.kill(os.getpid(), signal.SIGSEGV)
        finally:
            return
        print(e, type(e))
        raise e


@pytest.mark.skipif(not supports("memory"),
                    reason="System doesn't support memory")
def test_import_with_enough_memory() -> None:
    """
    Expects
    -------
    * Should import without a problem given enough memory
    """
    with limit(import_sklearn, memory=(1, "GB")) as lf:
        assert lf() is True


if __name__ == "__main__":
    """
    Use this to estimate memory limits. Output on my machine:

    * Before import =  17.83984375
    * After Import sklearn =  671.28515625

    """
    m = Monitor()
    print("Before import = ", m.memory("mb"))

    import sklearn  # noqa

    print("After Import sklearn = ", m.memory("mb"))