Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions Ports/LinuxPort/nativeSources/cn1_linux_embedded.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -133,6 +134,19 @@ static int cn1Dpi = 0;
static int cn1Initialized = 0;
static int cn1HeadlessForced = 0; /* enableHeadlessScreenshot was called */
static volatile int cn1Running = 1;
/* Set by the atexit hook before it puts the console's CRTC back. The EDT keeps
* running until the process ends and must not flip its own buffer back on top
* of the restored console; both present paths read this before a page flip.
* A flip the EDT already decided on can still land, so this narrows the
* window rather than closing it -- closing it would mean the exiting thread
* waiting on the EDT, which may be the thread that called exit. */
static atomic_int cn1Exiting = 0;

int cn1EmbeddedExiting(void); /* cn1_linux_gpu.h, not included without the GPU */

int cn1EmbeddedExiting(void) {
return atomic_load(&cn1Exiting);
}
static volatile int cn1CurrentModifiers = 0;
static int cn1HasTouch = 0, cn1HasPointer = 0;

Expand Down Expand Up @@ -695,7 +709,7 @@ static void cn1Present(CN1EmbRect damage) {
cn1DrawCursor(b, cn1CursorX, cn1CursorY);
}
#ifdef CN1_EMBEDDED_HAS_DRM
if (cn1Output == OUT_KMS) {
if (cn1Output == OUT_KMS && !cn1EmbeddedExiting()) {
if (drmModePageFlip(cn1DrmFd, cn1DrmCrtc, b->fbId, DRM_MODE_PAGE_FLIP_EVENT, 0) == 0) {
cn1FlipPending = 1;
cn1Front = target;
Expand All @@ -711,7 +725,7 @@ static void cn1Present(CN1EmbRect damage) {

static void cn1RestoreScreen(void) {
#ifdef CN1_EMBEDDED_GPU
cn1GpuShutdown();
cn1GpuShutdownAtExit();
#endif
#ifdef CN1_EMBEDDED_HAS_DRM
if (cn1DrmFd >= 0) {
Expand Down Expand Up @@ -1188,6 +1202,7 @@ static void cn1StartInput(void) {
/* ------------------------------------------------------------- natives */

static void cn1AtExit(void) {
atomic_store(&cn1Exiting, 1);
cn1Running = 0;
cn1RestoreScreen();
}
Expand Down
22 changes: 22 additions & 0 deletions Ports/LinuxPort/nativeSources/cn1_linux_gpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,10 @@ void cn1GpuPresent(void) {
if (!eglSwapBuffers(cn1gDpy, cn1gSurface)) {
return;
}
if (cn1EmbeddedExiting()) {
/* The console's CRTC is being put back; see cn1EmbeddedExiting. */
return;
}
bo = gbm_surface_lock_front_buffer(cn1gGbmSurface);
if (bo == 0) {
return;
Expand Down Expand Up @@ -1155,4 +1159,22 @@ void cn1GpuShutdown(void) {
cn1gTearDown();
}

void cn1GpuShutdownAtExit(void) {
/* Runs from exit() on whichever thread called System.exit, while the EDT
* may still be in the middle of a frame. Tearing the context down here
* freed the glyph atlas and terminated EGL under a texture upload in
* flight -- a segfault after the application had already finished. The
* process is going away, so its buffers are released by the kernel; only
* the screen state outlives it and needs putting back. */
/* Not even waiting out a pending flip: that runs the DRM event handler and
* swaps and releases the front and next buffers, which the EDT may be doing
* at this same moment in its own present -- the same race in a smaller
* window. Hiding the cursor plane is a single ioctl and touches no shared
* state; the display unit's drmModeSetCrtc then replaces the scanout
* whatever flip is still in flight. */
if (cn1gActive && cn1gScanout) {
cn1GpuCursor(0, 0, 0);
}
}

#endif /* CN1_LINUX_EMBEDDED && CN1_EMBEDDED_GPU */
8 changes: 8 additions & 0 deletions Ports/LinuxPort/nativeSources/cn1_linux_gpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ int cn1GpuCursor(int show, int px, int py);
* puts the console's CRTC back. */
void cn1GpuShutdown(void);

/* cn1GpuShutdown for the atexit path: hides the cursor plane and releases
* nothing, because another thread may still be drawing with the context. */
void cn1GpuShutdownAtExit(void);

/* Nonzero once the process is exiting; the GPU stops page flipping then.
* Defined by the display unit, cn1_linux_embedded.c. */
int cn1EmbeddedExiting(void);

#ifdef __cplusplus
}
#endif
Expand Down
6 changes: 6 additions & 0 deletions scripts/embedded/gpu-check/gpu_check.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@

typedef void (*Scene)(cairo_t* cr, cairo_surface_t* image, cairo_surface_t* target);

/* The display unit (cn1_linux_embedded.c) is not linked here; this harness
* never exits mid-frame. */
int cn1EmbeddedExiting(void) {
return 0;
}

static void fills(cairo_t* cr, cairo_surface_t* image, cairo_surface_t* target) {
(void) target;
(void) image;
Expand Down
Binary file modified scripts/initializr/common/src/main/resources/savor-modules.zip
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,28 @@ public void onBinary(WebSocketSession session, byte[] message, int offset, int l
@Override
public void onClose(WebSocketSession session, int code, String reason) {
Object username = session.getAttachment();
// Only an admitted connection; a refused one has no user and its end is
// expected. 1000 and 1001 are the app leaving. Anything else -- 1006
// above all, a connection lost with no Close -- is what to read first
// when pushes stop arriving, so it costs a line per dropped app.
if (username != null && code != 1000 && code != 1001) {
System.err.println("[live] websocket " + session.getId() + " of " + username
+ " closed " + code + (reason == null || reason.isEmpty() ? "" : " " + reason));
}
if (username instanceof String) {
hub.remove((String) username, session);
}
}

@Override
public void onError(WebSocketSession session, Exception error) {
// A read failure, a protocol error or a callback that threw: the backend
// reports it here and then closes, and without this line the only trace
// of why a channel ended would be the 1006 that onClose prints.
System.err.println("[live] websocket " + session.getId() + " of "
+ session.getAttachment() + " failed: " + error);
}

/// The `ticket` parameter of a query string, or null. A ticket is hex, so
/// there is nothing in it to decode.
static String ticket(String query) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,26 @@ private static void send(List<WebSocketSession> to, String text) {
try {
session.sendText(text);
} catch (IOException gone) {
// Said out loud: a dropped channel is otherwise invisible -- the
// app just stops hearing about its order, and a test sees only
// the connection end with no frame.
System.err.println("[live] websocket " + session.getId() + " of "
+ session.getAttachment() + " dropped; could not send "
+ frameType(text) + ": " + gone);
session.abort();
}
}
}

/// The `type` of a frame this class wrote, for a log line; never the
/// payload, which carries positions and order details.
static String frameType(String text) {
int at = text.indexOf("\"type\":\"");
if (at < 0) {
return "a frame";
}
at += 8;
int end = text.indexOf('"', at);
return end < 0 ? "a frame" : "a " + text.substring(at, end) + " frame";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class LiveSocketTest {
/// The last frame read: its opcode, and its payload as text.
private int opcode;
private byte[] payload;
/// Frames read so far in this test, for the message when one never comes.
private int framesRead;

@Test
void aTicketOpensTheChannelOnceAndPushesArrive() throws Exception {
Expand Down Expand Up @@ -192,7 +194,11 @@ private int closeCode() {
private void readFrame(Tcp connection) throws IOException {
int first = readByte(connection);
if (first < 0) {
throw new IOException("the connection ended with no frame");
// The server closed it. Why is in its own log, the "[live]" lines
// above this failure; say where the test had got to.
throw new IOException("the connection ended with no frame; " + framesRead
+ " frames read in this test, the last opcode " + opcode + ": "
+ (payload == null ? "none" : new String(payload, "UTF-8")));
}
opcode = first & 0xf;
int length = readByte(connection) & 0x7f;
Expand All @@ -208,6 +214,7 @@ private void readFrame(Tcp connection) throws IOException {
}
got += read;
}
framesRead++;
}

/// A client's frames are masked; a server refuses one that is not.
Expand Down
Loading