Skip to content

Commit a3a34c3

Browse files
committed
add broken pipe maangement so we cleanly allow | head -10 etc when pipe is broken and node is still trying to write.
1 parent 8719cf0 commit a3a34c3

File tree

5 files changed

+74
-5
lines changed

5 files changed

+74
-5
lines changed

cpp/binding.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <napi.h>
22
#include "include/utils/Logger.h"
33
#include "include/js/Connection.h"
4+
#include "include/common/platform.h"
45

56
static Napi::Value SetLogLevel(const Napi::CallbackInfo &info)
67
{
@@ -56,6 +57,9 @@ static Napi::Value SetLogFile(const Napi::CallbackInfo &info)
5657
// Initialize the module
5758
Napi::Object InitModule(Napi::Env env, Napi::Object exports)
5859
{
60+
// Initialize platform-specific signal handlers
61+
Platform::InitializeSignalHandlers();
62+
5963
// Initialize and export the Connection class
6064
mssql::Connection::Init(env, exports);
6165
exports.Set("setLogLevel", Napi::Function::New(env, SetLogLevel));

cpp/include/common/platform.h

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ inline void UnlockMutex(PlatformMutex* mutex) {
5656
#elif defined(PLATFORM_LINUX) || defined(PLATFORM_MACOS)
5757
#include <pthread.h>
5858
#include <unistd.h>
59+
#include <signal.h>
5960
typedef pthread_mutex_t PlatformMutex;
6061

6162
inline void InitializeMutex(PlatformMutex* mutex) {
@@ -116,4 +117,41 @@ class ScopedLock {
116117
// Prevent copying
117118
ScopedLock(const ScopedLock&) = delete;
118119
ScopedLock& operator=(const ScopedLock&) = delete;
119-
};
120+
};
121+
122+
// Platform-specific signal handling
123+
namespace Platform {
124+
// Initialize platform-specific signal handlers
125+
inline void InitializeSignalHandlers() {
126+
#if defined(PLATFORM_LINUX) || defined(PLATFORM_MACOS)
127+
// Ignore SIGPIPE on Unix-like systems to handle broken pipes gracefully
128+
// This prevents the process from terminating when writing to a closed pipe
129+
signal(SIGPIPE, SIG_IGN);
130+
#endif
131+
// Windows doesn't have SIGPIPE, it returns EPIPE errors instead
132+
}
133+
134+
// Check if a stream is still valid for writing
135+
template<typename Stream>
136+
inline bool IsStreamGood(Stream& stream) {
137+
return stream.good();
138+
}
139+
140+
// Safe stream write that handles broken pipes
141+
template<typename Stream>
142+
inline bool SafeStreamWrite(Stream& stream, const std::string& data) {
143+
if (!IsStreamGood(stream)) {
144+
return false;
145+
}
146+
147+
stream << data;
148+
149+
// Only flush if the write succeeded
150+
if (IsStreamGood(stream)) {
151+
stream.flush();
152+
return true;
153+
}
154+
155+
return false;
156+
}
157+
}

cpp/src/utils/Logger.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "Logger.h"
2+
#include "common/platform.h"
23

34
#include <chrono>
45
#include <codecvt>
@@ -21,8 +22,13 @@ Logger::Logger() {}
2122
Logger::~Logger() {
2223
// Flush console output before destruction
2324
if (logToConsole_) {
24-
std::cout.flush();
25-
std::cerr.flush();
25+
// Only flush if streams are still good (handles EPIPE)
26+
if (Platform::IsStreamGood(std::cout)) {
27+
std::cout.flush();
28+
}
29+
if (Platform::IsStreamGood(std::cerr)) {
30+
std::cerr.flush();
31+
}
2632
}
2733

2834
if (logFile_.is_open()) {
@@ -102,8 +108,11 @@ void Logger::Log(LogLevel level, const std::string& message) {
102108
logLine << "[" << timestamp.str() << "] [CPP] [" << threadId.str() << "] [" << levelStr << "] " << message;
103109

104110
if (logToConsole_) {
105-
std::cout << logLine.str() << std::endl;
106-
std::cout.flush(); // Ensure immediate flush for Node.js termination
111+
// Write to appropriate stream based on log level
112+
std::ostream& out = (level <= LogLevel::Warning) ? std::cerr : std::cout;
113+
114+
// Use platform-safe stream writing that handles broken pipes
115+
Platform::SafeStreamWrite(out, logLine.str() + "\n");
107116
}
108117

109118
if (logFile_.is_open()) {

lib/logger.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@ const { threadId, isMainThread } = require('worker_threads')
33
const fs = require('fs')
44
const path = require('path')
55

6+
// Handle EPIPE errors gracefully when piping output
7+
process.stdout.on('error', (err) => {
8+
if (err.code === 'EPIPE') {
9+
process.exit(0)
10+
}
11+
})
12+
13+
process.stderr.on('error', (err) => {
14+
if (err.code === 'EPIPE') {
15+
process.exit(0)
16+
}
17+
})
18+
619
// Mirror the C++ LogLevel enum
720
const LogLevel = {
821
SILENT: 0,

samples/javascript/simple-logging-config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
'use strict'
22

3+
// Handle SIGPIPE gracefully when output is piped
4+
process.on('SIGPIPE', () => {
5+
process.exit(0)
6+
})
7+
38
const sql = require('../../lib/sql')
49

510
// Example 1: Enable trace logging for debugging

0 commit comments

Comments
 (0)