From: Ian Rogers <irogers@google.com>
To: Peter Zijlstra <peterz@infradead.org>,
Ingo Molnar <mingo@redhat.com>,
Arnaldo Carvalho de Melo <acme@kernel.org>,
Namhyung Kim <namhyung@kernel.org>, Jiri Olsa <jolsa@kernel.org>,
Ian Rogers <irogers@google.com>,
Adrian Hunter <adrian.hunter@intel.com>,
James Clark <james.clark@linaro.org>,
Thomas Falcon <thomas.falcon@intel.com>,
Alice Rogers <alice.mei.rogers@gmail.com>,
Changbin Du <changbin.du@huawei.com>,
Tengda Wu <wutengda@huaweicloud.com>, tanze <tanze@kylinos.cn>,
Athira Rajeev <atrajeev@linux.ibm.com>,
Dapeng Mi <dapeng1.mi@linux.intel.com>,
linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org
Subject: [PATCH v1 02/13] perf python: Quietly stop processing events when a callback raises
Date: Fri, 2 Oct 2026 11:26:11 -0700 [thread overview]
Message-ID: <20261002182624.3259797-3-irogers@google.com> (raw)
In-Reply-To: <20261002182624.3259797-1-irogers@google.com>
When a session callback raises a python exception the callback returned
-1 causing reader__read_event() to print a "processing failed for event
of type: ..." error. This is redundant with the python exception that is
raised from process_events(), and corrupts the display of a TUI that
raises from a callback to deliberately stop processing, such as when
cancelling a background load.
Instead set session_done so that processing stops and return success,
the pending exception is then raised by process_events(). As a single
event may still cause further callbacks, for example multiple
call_return callbacks followed by a sample callback, skip calling python
while an exception is pending.
The session whose callback raised resets session_done after processing
so later sessions process all their events. As session_done is a global,
a session processed concurrently in another thread may be stopped early
too. Count the stop requests so that such a session raises an error
rather than silently dropping events.
Assisted-by: Antigravity:gemini-3.1-pro
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/python.c | 149 ++++++++++++++++++++++++++++++++-------
1 file changed, 122 insertions(+), 27 deletions(-)
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 95140dfaef9c..5b4963d5784d 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -4120,6 +4120,11 @@ struct pyrf_session {
*/
u64 call_return_last_db_id;
u64 sample_last_db_id;
+ /**
+ * @callback_raised: A python callback raised an exception and requested
+ * that event processing stops, see pyrf_session__callback_raised().
+ */
+ bool callback_raised;
char *vmlinux_name;
char *kallsyms_name;
char *symfs;
@@ -4439,6 +4444,55 @@ static PyObject *pyrf_call_return__new(struct call_return *cr)
return (PyObject *)pevent;
}
+/*
+ * Number of times a python callback has requested that event processing
+ * stops, see pyrf_session__callback_raised().
+ */
+static unsigned int pyrf_session__stop_requests;
+
+/*
+ * A python callback raised an exception.
+ *
+ * Returning an error to the session code would cause reader__read_event() to
+ * print a "processing failed for event of type: ..." error to stderr. This is
+ * noise, and in the case of a TUI corrupts the display, when a callback raises
+ * to deliberately stop processing (e.g. cancelling a background load) and the
+ * error message is redundant with the python exception that is raised anyway.
+ *
+ * Instead request that event processing stops by setting session_done and
+ * return success. The exception remains pending (PyErr_Occurred()) and
+ * pyrf_session__find_thread_events() returns NULL so python raises it.
+ *
+ * Note, session_done is a process-global rather than per-session. Were
+ * multiple sessions processing events concurrently in different python
+ * threads then an exception in one would stop all of them. Only the session
+ * whose callback raised clears session_done, and the other sessions detect
+ * the stop request, using pyrf_session__stop_requests, and raise an error
+ * rather than silently dropping events.
+ */
+static int pyrf_session__callback_raised(struct pyrf_session *psession)
+{
+ psession->callback_raised = true;
+ pyrf_session__stop_requests++;
+ session_done = 1;
+ return 0;
+}
+
+/*
+ * session_done stops the reader loops, the ordered_events and deferred sample
+ * flushes, and auxtrace__flush_events(), but some callbacks may still happen
+ * after a callback has raised. For example, a single sample may generate
+ * multiple call_return callbacks, and perf_session__flush_thread_stacks()
+ * delivers call returns for the remaining stack entries (skipping it would
+ * only defer those callbacks until the threads are deleted). Calling into
+ * python with an exception pending is an error, and the events would be
+ * discarded anyway, so callbacks check this and skip calling python.
+ */
+static bool pyrf_session__exception_pending(void)
+{
+ return PyErr_Occurred() != NULL;
+}
+
static int pyrf_session__call_return_process(struct call_return *cr,
u64 *parent_db_id,
void *data)
@@ -4446,7 +4500,7 @@ static int pyrf_session__call_return_process(struct call_return *cr,
struct pyrf_session *psession = data;
PyObject *pyevent, *ret;
- if (!psession->call_return)
+ if (!psession->call_return || pyrf_session__exception_pending())
return 0;
if (!cr->db_id)
@@ -4463,12 +4517,10 @@ static int pyrf_session__call_return_process(struct call_return *cr,
return -1;
ret = PyObject_CallFunctionObjArgs(psession->call_return, pyevent, NULL);
- if (!ret) {
- Py_DECREF(pyevent);
- return -1;
- }
- Py_DECREF(ret);
Py_DECREF(pyevent);
+ if (!ret)
+ return pyrf_session__callback_raised(psession);
+ Py_DECREF(ret);
return 0;
}
@@ -4480,6 +4532,9 @@ static int pyrf_session_tool__sample(const struct perf_tool *tool,
struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool);
u64 sample_db_id = ++psession->sample_last_db_id;
+ if (pyrf_session__exception_pending())
+ return 0;
+
if (psession->crp) {
struct addr_location al, addr_al;
struct thread *thread = NULL;
@@ -4510,6 +4565,10 @@ static int pyrf_session_tool__sample(const struct perf_tool *tool,
addr_location__exit(&addr_al);
addr_location__exit(&al);
+
+ /* A call_return callback may have raised. */
+ if (pyrf_session__exception_pending())
+ return 0;
}
if (psession->sample) {
@@ -4524,7 +4583,7 @@ static int pyrf_session_tool__sample(const struct perf_tool *tool,
ret = PyObject_CallFunction(psession->sample, "O", pyevent);
Py_DECREF(pyevent);
if (!ret)
- return -1;
+ return pyrf_session__callback_raised(psession);
Py_DECREF(ret);
}
return 0;
@@ -4536,9 +4595,19 @@ static int pyrf_session_tool__context_switch(const struct perf_tool *tool,
struct machine *machine)
{
struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool);
- PyObject *pyevent = pyrf_event__new(event, sample->evsel, psession->session, machine, NULL);
+ PyObject *pyevent;
PyObject *ret;
+ if (pyrf_session__exception_pending()) {
+ /*
+ * Skip calling python, but keep the machine's thread state
+ * consistent as perf_event__process_switch() would have been
+ * called had no python callback been registered.
+ */
+ return perf_event__process_switch(tool, event, sample, machine);
+ }
+
+ pyevent = pyrf_event__new(event, sample->evsel, psession->session, machine, NULL);
if (perf_event__process_switch(tool, event, sample, machine) < 0) {
Py_XDECREF(pyevent);
return -1;
@@ -4548,12 +4617,10 @@ static int pyrf_session_tool__context_switch(const struct perf_tool *tool,
return -ENOMEM;
ret = PyObject_CallFunction(psession->context_switch, "O", pyevent);
- if (!ret) {
- Py_DECREF(pyevent);
- return -1;
- }
- Py_DECREF(ret);
Py_DECREF(pyevent);
+ if (!ret)
+ return pyrf_session__callback_raised(psession);
+ Py_DECREF(ret);
return 0;
}
@@ -4563,21 +4630,23 @@ static int pyrf_session_tool__stat(const struct perf_tool *tool,
{
struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool);
struct evsel *evsel = evlist__id2evsel(session->evlist, event->stat.id);
- PyObject *pyevent = pyrf_event__new(event, /*evsel=*/NULL, psession->session,
- /*machine=*/NULL, NULL);
const char *name = evsel ? evsel__name(evsel) : "unknown";
+ PyObject *pyevent;
PyObject *ret;
+ if (pyrf_session__exception_pending())
+ return 0;
+
+ pyevent = pyrf_event__new(event, /*evsel=*/NULL, psession->session,
+ /*machine=*/NULL, NULL);
if (pyevent == NULL)
return -ENOMEM;
ret = PyObject_CallFunction(psession->stat, "Oz", pyevent, name);
- if (!ret) {
- Py_DECREF(pyevent);
- return -1;
- }
- Py_DECREF(ret);
Py_DECREF(pyevent);
+ if (!ret)
+ return pyrf_session__callback_raised(psession);
+ Py_DECREF(ret);
return 0;
}
@@ -4586,20 +4655,22 @@ static int pyrf_session_tool__stat_round(const struct perf_tool *tool,
union perf_event *event)
{
struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool);
- PyObject *pyevent = pyrf_event__new(event, /*evsel=*/NULL, psession->session,
- /*machine=*/NULL, NULL);
+ PyObject *pyevent;
PyObject *ret;
+ if (pyrf_session__exception_pending())
+ return 0;
+
+ pyevent = pyrf_event__new(event, /*evsel=*/NULL, psession->session,
+ /*machine=*/NULL, NULL);
if (pyevent == NULL)
return -ENOMEM;
ret = PyObject_CallFunction(psession->stat, "Oz", pyevent, NULL);
- if (!ret) {
- Py_DECREF(pyevent);
- return -1;
- }
- Py_DECREF(ret);
Py_DECREF(pyevent);
+ if (!ret)
+ return pyrf_session__callback_raised(psession);
+ Py_DECREF(ret);
return 0;
}
@@ -4680,6 +4751,7 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject
psession->crp = NULL;
psession->call_return_last_db_id = 0;
psession->sample_last_db_id = 0;
+ psession->callback_raised = false;
psession->vmlinux_name = NULL;
psession->kallsyms_name = NULL;
psession->symfs = NULL;
@@ -4847,11 +4919,34 @@ static void pyrf_session__delete(struct pyrf_session *psession)
static PyObject *pyrf_session__find_thread_events(struct pyrf_session *psession)
{
+ unsigned int stop_requests = pyrf_session__stop_requests;
int err;
CHECK_INITIALIZED(psession->session, "session");
+ psession->callback_raised = false;
err = perf_session__process_events(psession->session);
+ if (psession->callback_raised) {
+ /*
+ * Clear the early stop requested by this session's callback, in
+ * pyrf_session__callback_raised(), so that later sessions process
+ * all their events. That function is the only writer of
+ * session_done in the python module, so resetting it here after
+ * processing is sufficient and there is no need to also reset it
+ * before processing.
+ */
+ session_done = 0;
+ } else if (stop_requests != pyrf_session__stop_requests && !PyErr_Occurred()) {
+ /*
+ * A callback of a session being processed concurrently, in
+ * another thread, raised and set session_done which may have
+ * stopped processing this session's events early. Raise an error
+ * rather than silently dropping events.
+ */
+ PyErr_SetString(PyExc_RuntimeError,
+ "Processing events may have stopped early as a callback of a concurrently processed session raised an exception");
+ return NULL;
+ }
if (PyErr_Occurred())
return NULL;
--
2.56.0.rc1.315.gc6ed9934b7-goog
next prev parent reply other threads:[~2026-10-02 18:26 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-02 18:26 [PATCH v1 00/13] perf timechart/list/treport: Interactive Textual TUIs and perf python session improvements Ian Rogers
2026-10-02 18:26 ` [PATCH v1 01/13] perf session: Don't flush remaining events once processing is done Ian Rogers
2026-10-02 18:26 ` Ian Rogers [this message]
2026-10-02 18:26 ` [PATCH v1 03/13] perf python: Lazily copy events and samples from process_events Ian Rogers
2026-10-02 18:26 ` [PATCH v1 04/13] perf python: Lazily resolve sample callchains Ian Rogers
2026-10-02 18:26 ` [PATCH v1 05/13] perf python: Release the GIL while processing session events Ian Rogers
2026-10-02 18:26 ` [PATCH v1 06/13] perf list: Add a --tui option to launch ilist Ian Rogers
2026-10-02 18:26 ` [PATCH v1 07/13] perf test: Add a test for the ilist script Ian Rogers
2026-10-02 18:26 ` [PATCH v1 08/13] perf treport: Show the profile while it loads Ian Rogers
2026-10-03 9:31 ` Arnaldo Carvalho de Melo
2026-10-02 18:26 ` [PATCH v1 09/13] perf test: Add a test for the treport script Ian Rogers
2026-10-03 9:35 ` Arnaldo Carvalho de Melo
2026-10-02 18:26 ` [PATCH v1 10/13] perf timechart: Add an interactive --tui mode Ian Rogers
2026-10-02 18:26 ` [PATCH v1 11/13] perf test: Add a test for perf timechart --tui Ian Rogers
2026-10-02 18:26 ` [PATCH v1 12/13] perf timechart: Add a --live mode to the TUI Ian Rogers
2026-10-02 18:26 ` [PATCH v1 13/13] perf test: Test perf timechart --live Ian Rogers
2026-10-03 9:10 ` [PATCH v1 00/13] perf timechart/list/treport: Interactive Textual TUIs and perf python session improvements Arnaldo Carvalho de Melo
2026-10-03 9:12 ` Arnaldo Carvalho de Melo
2026-10-03 9:15 ` Arnaldo Carvalho de Melo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261002182624.3259797-3-irogers@google.com \
--to=irogers@google.com \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=alice.mei.rogers@gmail.com \
--cc=atrajeev@linux.ibm.com \
--cc=changbin.du@huawei.com \
--cc=dapeng1.mi@linux.intel.com \
--cc=james.clark@linaro.org \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mingo@redhat.com \
--cc=namhyung@kernel.org \
--cc=peterz@infradead.org \
--cc=tanze@kylinos.cn \
--cc=thomas.falcon@intel.com \
--cc=wutengda@huaweicloud.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®