GTK IOChannel问题:G_IO_IN事件触发异常致程序无限循环
Hey there, I’ve run into this exact issue before with GLib’s GIO channels and log files. Let’s break down why this is happening and how to fix it properly.
Why You’re Seeing the Infinite EOF Loop
When your GIO channel hits EOF on the log file, the underlying file descriptor stays in a "readable" state—because calling read() on it will immediately return EOF (no blocking). The GLib event loop interprets this as a continuous "ready to read" signal, so it keeps firing your DisplayLogs callback over and over, each time returning G_IO_STATUS_EOF.
This is totally normal behavior for file descriptors, but it’s not what we want when monitoring a log file that gets appended to over time. Let’s look at two solutions, with the first being the most robust for your use case.
Solution 1: Use GFileMonitor (Recommended)
Instead of relying on GIO channel’s G_IO_IN watch (which isn’t designed for monitoring appended files), use GLib’s GFileMonitor—it’s purpose-built to detect changes to files, like when new content is added. This avoids the EOF loop entirely.
Step 1: Track State with a Data Structure
First, create a struct to hold the state we need (so we can pass it to callbacks):
typedef struct { GtkTextBuffer *text_buffer; goffset last_read_position; GFile *log_file; } LogMonitorData;
Step 2: Initialize File Reading + Monitor
Replace your initial GIO channel setup with this code. It reads the full log file, then sets up a monitor to watch for changes:
// Get your TextView's buffer GtkTextBuffer *txtBuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tvLogs)); gtk_text_buffer_get_end_iter(txtBuf, &txtIter); // Open and read the initial log content GError *error = NULL; GFile *log_file = g_file_new_for_path("./errorlog.log"); GFileInputStream *stream = g_file_read(log_file, NULL, &error); if (stream) { // Read the entire file gchar *initial_content; gsize content_len; g_input_stream_read_all(G_INPUT_STREAM(stream), &initial_content, &content_len, NULL, &error); // Insert into TextView gtk_text_buffer_insert(txtBuf, &txtIter, initial_content, content_len); g_free(initial_content); // Record where we stopped reading (for future updates) goffset last_pos = g_seekable_tell(G_SEEKABLE(stream)); g_object_unref(stream); // Set up our monitor data LogMonitorData *monitor_data = g_new(LogMonitorData, 1); monitor_data->text_buffer = txtBuf; monitor_data->last_read_position = last_pos; monitor_data->log_file = g_object_ref(log_file); // Create the file monitor GFileMonitor *monitor = g_file_monitor_file(log_file, G_FILE_MONITOR_NONE, NULL, &error); if (monitor) { // Connect the change event to our callback g_signal_connect(monitor, "changed", G_CALLBACK(on_log_updated), monitor_data); } else { g_warning("Failed to create log monitor: %s", error->message); g_error_free(error); } } else { g_warning("Couldn't open log file: %s", error->message); g_error_free(error); } g_object_unref(log_file);
Step 3: Handle File Change Events
Add this callback to read new content when the log file is updated:
static void on_log_updated(GFileMonitor *monitor, GFile *file, GFile *unused, GFileMonitorEvent event, gpointer user_data) { LogMonitorData *data = user_data; // Only react to file content changes if (event != G_FILE_MONITOR_EVENT_CHANGED) { return; } GError *error = NULL; GFileInputStream *stream = g_file_read(data->log_file, NULL, &error); if (!stream) { g_warning("Failed to open log for update: %s", error->message); g_error_free(error); return; } // Jump to where we left off last time if (!g_seekable_seek(G_SEEKABLE(stream), data->last_read_position, G_SEEK_SET, NULL, &error)) { g_warning("Couldn't seek to last read position: %s", error->message); g_error_free(error); g_object_unref(stream); return; } // Read only the new content gchar *new_content; gsize new_content_len; if (g_input_stream_read_all(G_INPUT_STREAM(stream), &new_content, &new_content_len, NULL, &error)) { if (new_content_len > 0) { // Insert new content into the TextView GtkTextIter end_iter; gtk_text_buffer_get_end_iter(data->text_buffer, &end_iter); gtk_text_buffer_insert(data->text_buffer, &end_iter, new_content, new_content_len); // Update our last read position data->last_read_position = g_seekable_tell(G_SEEKABLE(stream)); } g_free(new_content); } else { g_warning("Failed to read new log content: %s", error->message); g_error_free(error); } g_object_unref(stream); }
Solution 2: Fix the Existing GIO Channel Callback
If you want to stick with your original GIO channel approach, you need to stop the watch when hitting EOF (since it will keep firing otherwise). You’ll also need a way to restart the watch when new content is added (we’d still recommend pairing this with GFileMonitor to trigger restarts).
Here’s the modified callback:
gboolean DisplayLogs(GIOChannel *chanErr, GIOCondition cond, gpointer data) { GtkWidget *tvLogs = GTK_WIDGET(gtk_builder_get_object(builder, "tvLogs")); g_assert(tvLogs); GtkTextBuffer *txtBuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tvLogs)); GtkTextIter txtIter; gchar *buf; gsize bufLen; GError *err = NULL; GIOStatus ret = g_io_channel_read_line(chanErr, &buf, &bufLen, NULL, &err); if (ret == G_IO_STATUS_NORMAL) { // We got new content—update the TextView gtk_text_buffer_get_end_iter(txtBuf, &txtIter); gtk_text_buffer_insert(txtBuf, &txtIter, buf, bufLen); g_free(buf); // Keep the watch active return TRUE; } else { switch (ret) { case G_IO_STATUS_ERROR: g_warning("Log read error: %s", err ? err->message : "Unknown error"); break; case G_IO_STATUS_EOF: g_warning("Reached end of log—stopping IO watch"); // Return FALSE to remove the watch (it's useless now until new content is added) break; case G_IO_STATUS_AGAIN: g_warning("Temporary read failure—keeping watch active"); // Keep the watch going, since the issue is temporary return TRUE; } if (err) g_error_free(err); // Stop the watch on error or EOF return FALSE; } }
Final Notes
The GFileMonitor approach is far better for log file monitoring—it’s efficient, designed for this exact use case, and avoids the EOF loop entirely. The second solution works but requires extra work to restart the watch when new content arrives.
内容的提问来源于stack exchange,提问作者Chouchenn

