Q:

How to do I write signal-safe Python code?

I'll start with an example code, that "should never" fail:
counter1 = 0
counter2 = 0

def increment():
    global counter1, counter2
    counter1 += 1
    counter2 += 1

while True:
    try:
        increment()
    except:
        pass
    assert counter1 == counter2

The counters represent an internal structure that should keep its integrity no matter what. By a quick look, the assertion will never be False, and the structure will be intact.
One small signal however, that occurs in the middle of the function (for example SIGINT or KeyboardInterrupt in Python) causes the internal structure to break. In real-world scenario it may cause memory corruption, deadlocks, and all other types of mayhem.
Is there any way to make this function signal safe? If any arbitrary code can run anywhere and cause it to malfunction, how can we program library code that is safe and secure?
Even if we attempt to guard it using try...finally..., we might still receive a signal at the finally and prevent it from running.
While the example is in Python, I believe this question applies to all programming languages as a whole.
EDIT:
Do keep in mind the counters are just an example. In my actual use case it's different Locks and threading.Events. There's no real way (that I know of) to make the operation atomic.

A:

One solution to your example is to update the data atomically, if possible:
counters = [0, 0]

def increment():
    global counters

    # "complex" preparation. not atomic at all
    next0 = counters[0] + 1
    next1 = counters[1] + 1
    temp = [next0, next1]

    # actual atomic update
    counters = temp

try:
    while True:
        increment()
finally:
    assert counters[0] == counters[1]

No matter where the execution of the code stops, counters will always be in a consistent state, even without any cleanup.
Of course, this is a special case, which can't be always applied. A more general concept would be transactions where multiple actions are bundled and either all get executed or none.
Errors during rollback/abort of a transaction can be still problematic, though.
There is a transaction package, but I did not look into that.

