v1.0.15.3 — non-blocking logger (fix install freeze), URL-encode pack file paths, news ordering by mtime
This commit is contained in:
+61
-14
@@ -9,6 +9,9 @@ import java.nio.file.Paths;
|
|||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
public class LauncherLogger {
|
public class LauncherLogger {
|
||||||
@@ -17,6 +20,28 @@ public class LauncherLogger {
|
|||||||
private static boolean initialized = false;
|
private static boolean initialized = false;
|
||||||
private static final ReentrantLock lock = new ReentrantLock();
|
private static final ReentrantLock lock = new ReentrantLock();
|
||||||
|
|
||||||
|
private static final int STDOUT_QUEUE_CAPACITY = 2048;
|
||||||
|
private static final BlockingQueue<String> stdoutQueue = new LinkedBlockingQueue<>(STDOUT_QUEUE_CAPACITY);
|
||||||
|
private static final Thread stdoutWriterThread;
|
||||||
|
|
||||||
|
static {
|
||||||
|
stdoutWriterThread = new Thread(() -> {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
String line = stdoutQueue.take();
|
||||||
|
System.out.println(line);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return;
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// stdout may be a closed or full pipe; never let it kill the writer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "launcher-log-stdout");
|
||||||
|
stdoutWriterThread.setDaemon(true);
|
||||||
|
stdoutWriterThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
public static synchronized void init() {
|
public static synchronized void init() {
|
||||||
if (initialized) return;
|
if (initialized) return;
|
||||||
initialized = true;
|
initialized = true;
|
||||||
@@ -65,31 +90,53 @@ public class LauncherLogger {
|
|||||||
String ts = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
|
String ts = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
|
||||||
String line = "[" + ts + "] [" + level + "] " + msg;
|
String line = "[" + ts + "] [" + level + "] " + msg;
|
||||||
|
|
||||||
System.out.println(line);
|
// stdout goes through a dedicated writer thread with a bounded queue so a
|
||||||
|
// full/hung stdout pipe (e.g. javaw piped to the parent exe) can never
|
||||||
|
// block application threads.
|
||||||
|
offerStdout(line);
|
||||||
if (t != null) {
|
if (t != null) {
|
||||||
StringWriter sw = new StringWriter();
|
StringWriter sw = new StringWriter();
|
||||||
PrintWriter pw = new PrintWriter(sw);
|
PrintWriter pw = new PrintWriter(sw);
|
||||||
t.printStackTrace(pw);
|
t.printStackTrace(pw);
|
||||||
pw.flush();
|
pw.flush();
|
||||||
System.err.print(sw.toString());
|
offerStdout(sw.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logFile != null) {
|
if (logFile != null) {
|
||||||
lock.lock();
|
// Never block indefinitely on the file write; give up after a short wait
|
||||||
|
// so a stuck writer cannot deadlock every logging thread.
|
||||||
|
boolean acquired = false;
|
||||||
try {
|
try {
|
||||||
Files.writeString(logFile, line + "\n", StandardOpenOption.APPEND);
|
acquired = lock.tryLock(200, TimeUnit.MILLISECONDS);
|
||||||
if (t != null) {
|
} catch (InterruptedException ignored) {
|
||||||
StringWriter sw = new StringWriter();
|
Thread.currentThread().interrupt();
|
||||||
PrintWriter pw = new PrintWriter(sw);
|
}
|
||||||
t.printStackTrace(pw);
|
if (acquired) {
|
||||||
pw.flush();
|
try {
|
||||||
Files.writeString(logFile, sw.toString(), StandardOpenOption.APPEND);
|
Files.writeString(logFile, line + "\n", StandardOpenOption.APPEND);
|
||||||
|
if (t != null) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
PrintWriter pw = new PrintWriter(sw);
|
||||||
|
t.printStackTrace(pw);
|
||||||
|
pw.flush();
|
||||||
|
Files.writeString(logFile, sw.toString(), StandardOpenOption.APPEND);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("[LauncherLogger] write error: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
|
||||||
System.err.println("[LauncherLogger] write error: " + e.getMessage());
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void offerStdout(String line) {
|
||||||
|
try {
|
||||||
|
if (!stdoutQueue.offer(line, 10, TimeUnit.MILLISECONDS)) {
|
||||||
|
// Queue full; drop rather than block the caller.
|
||||||
|
}
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>1.0.15</revision>
|
<revision>1.0.15</revision>
|
||||||
<hotfix>2</hotfix>
|
<hotfix>3</hotfix>
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|||||||
+6
-6
@@ -4,7 +4,7 @@ import os
|
|||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse, quote
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
@@ -1128,9 +1128,9 @@ async def get_pack_diff(
|
|||||||
for path, entry in server_files.items():
|
for path, entry in server_files.items():
|
||||||
client_hash = body.get(path)
|
client_hash = body.get(path)
|
||||||
if client_hash is None or client_hash != entry.hash:
|
if client_hash is None or client_hash != entry.hash:
|
||||||
url = f"/pack/{pack_name}/file/{path}"
|
url = f"/pack/{pack_name}/file/{quote(path, safe='/')}"
|
||||||
if preset:
|
if preset:
|
||||||
url += f"?preset={preset}"
|
url += f"?preset={quote(preset, safe='')}"
|
||||||
to_download.append({
|
to_download.append({
|
||||||
"path": path,
|
"path": path,
|
||||||
"url": url,
|
"url": url,
|
||||||
@@ -1969,8 +1969,9 @@ async def list_news():
|
|||||||
return {"news": []}
|
return {"news": []}
|
||||||
|
|
||||||
news_list = []
|
news_list = []
|
||||||
for f in sorted(NEWS_DIR.iterdir()):
|
files = [f for f in NEWS_DIR.iterdir() if f.is_file() and f.suffix == ".txt"]
|
||||||
if f.is_file() and f.suffix == ".txt":
|
files.sort(key=lambda f: f.stat().st_mtime, reverse=True)
|
||||||
|
for f in files:
|
||||||
try:
|
try:
|
||||||
content = f.read_text(encoding="utf-8").strip().split("\n")
|
content = f.read_text(encoding="utf-8").strip().split("\n")
|
||||||
if len(content) >= 4:
|
if len(content) >= 4:
|
||||||
@@ -1988,7 +1989,6 @@ async def list_news():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Failed to read news file {f.name}: {e}")
|
logger.warning(f"Failed to read news file {f.name}: {e}")
|
||||||
|
|
||||||
news_list.reverse()
|
|
||||||
return {"news": news_list}
|
return {"news": news_list}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user