Skip to content

Commit

Permalink
cvlib: Update doWithTimeout to block recursive calling
Browse files Browse the repository at this point in the history
Stop doWithTimeout from being able to be recursively called.
Also reset alarm handler to previous default on timeout func
completion

Change-Id: I43dec86ef8ccaeca401e3a8cd051266ba3e871a2
  • Loading branch information
cianmcgrath committed Jan 24, 2025
1 parent bf22782 commit 8fed532
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 3 deletions.
23 changes: 20 additions & 3 deletions cloudvision/cvlib/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ class LoggingLevel(IntEnum):
Critical = 5


def monitorTimerHandler(signum, frame):
'''
A handler function for the timer that will raise our custom exception
Needs to be declared out here so that we can compare it to ensure that
no ongoing timer is running
'''
raise TimeoutExpiry


class Context:
'''
Context object that stores a number of system and user-defined parameters:
Expand Down Expand Up @@ -360,11 +369,17 @@ def doWithTimeout(f, timeout: int):
Takes a function and a timeout in seconds.
Will call and return the result of f, but raises a cvlib.TimeoutExpiry
exception if it runs longer than <timeout>
NOTE: If there is an attempt to recursively call this function, an InvalidContextException
will be raised.
'''

# A handler function for the timer that will raise our custom exception
def monitorTimerHandler(signum, frame):
raise TimeoutExpiry
# Store the default alarm signal so we can set it back again when we're done
originalSigHandler = signal.getsignal(signal.SIGALRM)
# Ensure that the current signal is not the monitorTimerHandler which would indicate a
# recursive call. This is not supported as due to any ongoing alarm timeouts of previous
# calls will be be overwritten and we can only have 1 alarm at a time.
if originalSigHandler is monitorTimerHandler:
raise InvalidContextException("Cannot recursively call doWithTimeout")

# Set up a signal handler that will cause a signal.SIGALRM signal to trigger our timer
# handler
Expand All @@ -377,6 +392,8 @@ def monitorTimerHandler(signum, frame):
finally:
# Always turn off the alarm, whether returning a value or propagating an exception
signal.alarm(0)
# Reset the alarm signal handler
signal.signal(signal.SIGALRM, originalSigHandler)

def initializeStudioCtxFromArgs(self):
'''
Expand Down
31 changes: 31 additions & 0 deletions test/cvlib/context/test_timeout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright (c) 2025 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the COPYING file.

import pytest

from cloudvision.cvlib import Context, InvalidContextException, User


def test_do_with_timeout():
ctx = Context(user=User("test_user", "123"))

# Function that is ok to use in a doWithTimeout
def okFunc():
print("ok")

# Function that is not ok to use in a doWithTimeout
def exceptionFunc():
ctx.doWithTimeout(okFunc, 5)

# Run a function that will pass, though will have set up the
# necessary alarms and then unset them
ctx.doWithTimeout(okFunc, 5)

# Run a function that will fail as it is recursively calling doWithTimeout
with pytest.raises(InvalidContextException) as exc_info:
ctx.doWithTimeout(exceptionFunc, 5)
assert "Cannot recursively call doWithTimeout" in str(exc_info.value)

# Run ok function once again to make sure that previous exception still unset the handler
ctx.doWithTimeout(okFunc, 5)

0 comments on commit 8fed532

Please sign in to comment.