3

I tried to cancel thread with c-function pthread_cancel(). Also I check canceltype and cancelstate: it has default values: PTHREAD_CANCEL_DEFERRED and PTHREAD_CANCEL_ENABLE

import threading
import ctypes
from time import sleep
import os
import sys

libc = ctypes.CDLL("libc.so.6")

pthread_t = ctypes.c_ulong

PTHREAD_CANCEL_DEFERRED = 0
PTHREAD_CANCEL_ASYNCHRONOUS = 1
canceltype = ctypes.c_int
pthread_setcanceltype = libc.pthread_setcanceltype
pthread_setcanceltype.argtypes = [ctypes.c_int, ctypes.POINTER(canceltype)]
pthread_setcanceltype.restype = ctypes.c_int

PTHREAD_CANCEL_ENABLE = 0
PTHREAD_CANCEL_DISABLE = 1
cancelstate = ctypes.c_int
pthread_setcancelstate = libc.pthread_setcancelstate
pthread_setcancelstate.argtypes = [ctypes.c_int, ctypes.POINTER(cancelstate)]
pthread_setcancelstate.restype = ctypes.c_int

pthread_cancel = libc.pthread_cancel
pthread_cancel.argtypes = [pthread_t]
pthread_cancel.restype = ctypes.c_int

def terminate_thread(thread):
    if not thread.is_alive():
        return
    #exc = ctypes.py_object(SystemExit)
    #res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread.ident), exc)
    res = pthread_cancel(thread.ident)
    #if res == 0:
    #    raise ValueError("Can't stop thread.")
    if res != 0:
        #ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
        raise SystemError("Error while stopping thread:",res)

def thread2():
    prev_type = canceltype()
    ret = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, ctypes.byref(prev_type))
    if ret != 0:
        err = ctypes.get_errno()
        print(f"pthread_setcanceltype failed: errno={err}")
        return
    print('prev_type=',prev_type)

    ret = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, ctypes.byref(prev_type))
    if ret != 0:
        err = ctypes.get_errno()
        print(f"pthread_setcancelstate failed: errno={err}")
        return
    print('prev_state=',prev_type)

    print("thread2 start reading")
    #input()
    sleep(1000) # for sleep() and for input() it behaves the same
    print("thread2 ends")

thr2 = threading.Thread(target=thread2)
thr2.start()

sleep(2)

print("killing thr2")
terminate_thread(thr2)
thr2.join()
print("main exit")

Why this code stuck in thr2.join() with output:

prev_type= c_int(0)
prev_state= c_int(0)
thread2 start reading
killing thr2

But this code:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void * thread2(void *){
    printf("thread2 start reading\n");
    getchar();
    printf("thread2 ends\n");
}

int main(){
    printf("PTHREAD_CANCEL_DEFERRED = %d\nPTHREAD_CANCEL_ASYNCHRONOUS = %d\nPTHREAD_CANCEL_ENABLE = %d\nPTHREAD_CANCEL_DISABLE = %d\n",
PTHREAD_CANCEL_DEFERRED,PTHREAD_CANCEL_ASYNCHRONOUS,PTHREAD_CANCEL_ENABLE,PTHREAD_CANCEL_DISABLE);

    pthread_t thr2;
    pthread_create(&thr2, NULL, thread2, NULL);

    sleep(2);

    printf("killing thr2\n");
    pthread_cancel(thr2);
    pthread_join(thr2, NULL);
    printf("main exit\n");
}

normally treat cancellation of thread? With output:

PTHREAD_CANCEL_DEFERRED = 0
PTHREAD_CANCEL_ASYNCHRONOUS = 1
PTHREAD_CANCEL_ENABLE = 0
PTHREAD_CANCEL_DISABLE = 1
thread2 start reading
killing thr2
main exit

sleep(), read() - is cancellation points

Even if I set PTHREAD_CANCEL_ASYNCHRONOUS at the beginning of thread and remove htr2.join() at the end of program, the program continues to hang.

UPD

I found error: I should use thread.native_id instead thread.ident. And as result I get segmentation fault as expected.

3
  • 1
    Python threads cannot be cancelled but processes can. Is there a reason why you cannot use a child process instead? Commented Jul 4, 2025 at 12:30
  • Python interpteter isn't designed to unwind C-level thread stacks via pthread_cancel, it can corrupt the interpreter state. There is an official ctypes support which can raise exceptions , check the use of ctypes.pythonapi.PyThreadState_SetAsyncExc stackoverflow.com/a/325528/3445290 .Otherwise you can try mutliprocessing as @Booboo mentioned above , they can be killed. Commented Jul 4, 2025 at 15:45
  • @Ritesh it raise exception only after blocking call returs. I need interrupt blocking call inside thread Commented Jul 4, 2025 at 16:04

2 Answers 2

0

Python doesn't support force-killing threads (Python threads are not raw pthreads). The safe, idiomatic way is to:

  • Use a threading.Event() to signal cancellation.
  • Have the thread check this flag periodically.

Something like:

import threading
import time

stop_event = threading.Event()

def worker():
    print("Thread started")
    for i in range(1000):
        if stop_event.is_set():
            print("Thread exiting due to stop_event")
            return
        time.sleep(1)
    print("Thread finished")

thr = threading.Thread(target=worker)
thr.start()

time.sleep(5)
print("Sending stop signal")
stop_event.set()
thr.join()
print("Main exit")
Sign up to request clarification or add additional context in comments.

How to check this flag periodically if my thread wait data from net or from stdin?
Since Python threads can't be force-killed, avoid blocking operations like recv() or read() without a timeout. Instead, use select() or set timeouts so the thread can periodically check something like a threading.Event() and exit cleanly.
0

Safe solution in UNIX for custom input functions

main idea: if you register signal handler (that do nothing) with sigaction() that signal (directed exactly to blocking thread) break blocking call with EINTR in errno. This method will not work with standard python input/output operations because it check this case I don't know how to make cpython function PyErr_CheckSignals() to return True.

Another peculiarity is that thread.native_id is not a pthread_t.

interruptable.c:

#define PY_SSIZE_T_CLEAN
#include <stdio.h>
#include <signal.h>
#include <string.h>

#include <Python.h>

// hook for signal
void hook(int sig) {
    //printf("i am hook of %zx\n",(size_t)pthread_self());
}

// custom read function, return str
static PyObject* read_line(PyObject *self, PyObject *args) {
    char buf[100];
    //printf("start read()\n");
    char * x;
    Py_BEGIN_ALLOW_THREADS
    x = fgets(buf, sizeof(buf),stdin);
    Py_END_ALLOW_THREADS
    //printf("stop read()\n");
    if (!x) {
        Py_RETURN_NONE; // EOF или ошибка
    }
    return PyUnicode_FromString(buf);
}

// set signal hook via sigaction
// and return pthread_self() as bytes
static PyObject* get_pthread(PyObject* self, PyObject* args) {
    struct sigaction sa, osa;
    sa.sa_handler = hook;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_INTERRUPT;
    if(sigaction(SIGUSR1,&sa,&osa)==-1){
        fprintf(stderr,"failed to setup signal hook\n");
        exit(1);
    }

    pthread_t t = pthread_self();
    //printf("return pthread %zx\n",(size_t)t);
    return PyBytes_FromStringAndSize((const char *)&t, (Py_ssize_t)sizeof(t));
}

// send SIGUSR1 to thread described by first argument 
// as pthread_t, serialised in bytes
static PyObject *
thread_kill(PyObject *self, PyObject *args)
{
    const char *buf;
    Py_ssize_t buflen;

    if (!PyArg_ParseTuple(args, "y#", &buf, &buflen))
        return NULL;

    if (buflen != sizeof(pthread_t)) {
        PyErr_Format(PyExc_ValueError,
                     "Invalid size: got %zd bytes, expected %zu",
                     buflen, sizeof(pthread_t));
        return NULL;
    }

    pthread_t t;
    memcpy(&t, buf, sizeof(pthread_t));

    printf("send SIGUSR1 to %zx\n",(size_t)t);
    int ret = pthread_kill(t, SIGUSR1);
    if (ret != 0) {
        PyErr_Format(PyExc_OSError,
                     "pthread_kill failed with code %d", ret);
        return NULL;
    }

    Py_RETURN_NONE;
}

static PyMethodDef MyMethods[] = {
    {"get_pthread", get_pthread, METH_NOARGS, 
        "Return raw bytes of pthread_self() (memcpy of pthread_t)."},
    {"thread_kill", thread_kill, METH_VARARGS,
     "Send SIGUSR1 to pthread represented by bytes."},
    {"read_line", read_line, METH_NOARGS, "Read a line from stdin"},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef mymodule = {
    PyModuleDef_HEAD_INIT,
    "interruptable", // Имя модуля
    NULL,
    -1,
    MyMethods
};

PyMODINIT_FUNC PyInit_interruptable(void) {
    return PyModule_Create(&mymodule);
}

setup.py:

# python3 setup.py build_ext --inplace
from setuptools import setup, Extension

setup(
    name="interruptable",
    ext_modules=[
        Extension("interruptable", ["interruptable.c"])
    ]
)

kill_thread.py:

import threading
import ctypes
from time import sleep
import interruptable

# pthread_t serialised as bytes
# represent thr2
thr2_pthread = None

# standard python trick:
def raise_in_thread(tid, exctype):
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
        ctypes.c_long(tid),
        ctypes.py_object(exctype)
    )
    if res == 0:
        raise ValueError("Invalid thread ID")
    elif res > 1:
        ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), None)
        raise SystemError("PyThreadState_SetAsyncExc failed")

def thread2():
    global thr2_pthread
    thr2_pthread = interruptable.get_pthread()
    print(f"thread2 start reading")

    print(interruptable.read_line())

    print("thread2 ends")

# create and run thread
thr2 = threading.Thread(target=thread2)
thr2.start()

# wait 2 secnds
sleep(2)

if thr2.is_alive():
    #print("killing thr2")
    raise_in_thread(thr2.ident, SystemExit)
    # send signal to thread
    interruptable.thread_kill(thr2_pthread)
thr2.join()
print("main exit")

I've also tryed to pthread_cansel(). It cancel thread without segfaults, but at end program still waits something

Comments

Your Answer

Draft saved
Draft discarded

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.