mirror of
https://github.com/ksherlock/TwoTerm.git
synced 2024-06-09 13:29:31 +00:00
move all the child/fd/kevent monitoring to a dedicated thread / class.
This commit is contained in:
parent
8d282293f0
commit
e0bc21d663
20
ChildMonitor.h
Normal file
20
ChildMonitor.h
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
// ChildMonitor.h
|
||||||
|
// TwoTerm
|
||||||
|
//
|
||||||
|
// Created by Kelvin Sherlock on 1/31/2018.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
@class TermWindowController;
|
||||||
|
@interface ChildMonitor : NSObject {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
+(id)monitor;
|
||||||
|
|
||||||
|
-(void)removeController: (TermWindowController *)controller;
|
||||||
|
-(void)addController: (TermWindowController *)controller pid: (pid_t)pid fd: (int)fd;
|
||||||
|
|
||||||
|
@end
|
239
ChildMonitor.mm
Normal file
239
ChildMonitor.mm
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
//
|
||||||
|
// ChildMonitor.m
|
||||||
|
// TwoTerm
|
||||||
|
//
|
||||||
|
// Created by Kelvin Sherlock on 1/31/2018.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "ChildMonitor.h"
|
||||||
|
#import "TermWindowController.h"
|
||||||
|
|
||||||
|
#include "Lock.h"
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/event.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct entry {
|
||||||
|
pid_t pid;
|
||||||
|
int fd;
|
||||||
|
TermWindowController *controller;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
typedef std::vector<entry> entry_vector;
|
||||||
|
|
||||||
|
entry_vector::iterator find_controller(entry_vector &table, TermWindowController *controller) {
|
||||||
|
return std::find_if(table.begin(), table.end(), [=](const entry &e){
|
||||||
|
return e.controller == controller;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
entry_vector::iterator find_pid(entry_vector &table, pid_t pid) {
|
||||||
|
return std::find_if(table.begin(), table.end(), [=](const entry &e){
|
||||||
|
return e.pid == pid;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
entry_vector::iterator find_fd(entry_vector &table, int fd) {
|
||||||
|
return std::find_if(table.begin(), table.end(), [=](const entry &e){
|
||||||
|
return e.fd == fd;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* return NO on EOF */
|
||||||
|
BOOL read(int fd, TermWindowController *controller) {
|
||||||
|
size_t total = 0;
|
||||||
|
for (;;) {
|
||||||
|
uint8_t buffer[2048];
|
||||||
|
ssize_t ok = ::read(fd, buffer, sizeof(buffer));
|
||||||
|
if (ok == 0) return total > 0;
|
||||||
|
if (ok < 1) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
if (errno == EAGAIN) return YES;
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
[controller processData: buffer size: ok];
|
||||||
|
if (ok < sizeof(buffer)) return YES;
|
||||||
|
total += ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@interface ChildMonitor() {
|
||||||
|
std::vector<entry> _table;
|
||||||
|
int _kq;
|
||||||
|
Lock _lock;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
@implementation ChildMonitor
|
||||||
|
|
||||||
|
+(id)monitor {
|
||||||
|
static ChildMonitor *me = nil;
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{
|
||||||
|
me = [ChildMonitor new];
|
||||||
|
[NSThread detachNewThreadSelector: @selector(run) toTarget: me withObject: nil];
|
||||||
|
});
|
||||||
|
return me;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(id)init {
|
||||||
|
if ((self = [super init])) {
|
||||||
|
|
||||||
|
_kq = kqueue();
|
||||||
|
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)dealloc {
|
||||||
|
_lock.lock();
|
||||||
|
close(_kq);
|
||||||
|
for (const auto &e : _table) {
|
||||||
|
if (e.fd >= 0) close(e.fd);
|
||||||
|
if (e.pid > 0) kill(e.pid, SIGHUP);
|
||||||
|
if (e.controller) [e.controller release];
|
||||||
|
}
|
||||||
|
_lock.unlock();
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)removeController: (TermWindowController *)controller {
|
||||||
|
|
||||||
|
if (!controller) return;
|
||||||
|
|
||||||
|
Locker l(_lock);
|
||||||
|
|
||||||
|
auto iter = find_controller(_table, controller);
|
||||||
|
if (iter != _table.end()) {
|
||||||
|
[iter->controller release];
|
||||||
|
iter->controller = nil;
|
||||||
|
if (iter->pid > 0) kill(iter->pid, SIGHUP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)addController: (TermWindowController *)controller pid: (pid_t)pid fd: (int)fd {
|
||||||
|
|
||||||
|
NSLog(@"Adding pid: %d fd: %d", pid, fd);
|
||||||
|
|
||||||
|
int flags;
|
||||||
|
// non-blocking io.
|
||||||
|
if (fcntl(fd, F_GETFL, &flags) < 0) flags = 0;
|
||||||
|
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
|
||||||
|
Locker l(_lock);
|
||||||
|
|
||||||
|
_table.emplace_back(entry{pid, fd, [controller retain]});
|
||||||
|
|
||||||
|
struct kevent events[2] = {};
|
||||||
|
|
||||||
|
EV_SET(&events[0], fd, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, NULL);
|
||||||
|
EV_SET(&events[1], pid, EVFILT_PROC, EV_ADD | EV_ONESHOT | EV_RECEIPT, NOTE_EXIT | NOTE_EXITSTATUS, 0, NULL);
|
||||||
|
|
||||||
|
kevent(_kq, events, 2, NULL, 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void) run {
|
||||||
|
struct kevent events[16] = {};
|
||||||
|
|
||||||
|
for(;;) {
|
||||||
|
|
||||||
|
@autoreleasepool {
|
||||||
|
|
||||||
|
int n = kevent(_kq, NULL, 0, events, 2, NULL);
|
||||||
|
|
||||||
|
if (n < 0) {
|
||||||
|
NSLog(@"kevent: %s", strerror(errno));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (n == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Locker l(_lock);
|
||||||
|
|
||||||
|
// should process twice, first for reading, second for dead children.
|
||||||
|
|
||||||
|
std::for_each(events, events + n, [&](const struct kevent &e){
|
||||||
|
|
||||||
|
if (e.filter != EVFILT_READ) return;
|
||||||
|
int fd = (int)e.ident;
|
||||||
|
if (e.flags & EV_EOF) {
|
||||||
|
NSLog(@"EV_EOF %d", fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.flags & EV_ERROR) {
|
||||||
|
NSLog(@"EV_ERROR %d", fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto iter = find_fd(_table, fd);
|
||||||
|
if (iter == _table.end() || iter->controller == nil) {
|
||||||
|
|
||||||
|
NSLog(@"Closing fd %d (not found)", fd);
|
||||||
|
close(fd); // should automatically remove itself from kevent
|
||||||
|
iter->fd = -1;
|
||||||
|
} else {
|
||||||
|
BOOL ok = read(fd, iter->controller);
|
||||||
|
if (!ok) {
|
||||||
|
NSLog(@"Closing fd %d (eof)", fd);
|
||||||
|
close(fd); // should automatically remove itself from kevent
|
||||||
|
iter->fd = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::for_each(events, events + n, [&](const struct kevent &e){
|
||||||
|
|
||||||
|
if (e.filter != EVFILT_PROC) return;
|
||||||
|
|
||||||
|
pid_t pid = (pid_t)e.ident;
|
||||||
|
|
||||||
|
int status = 0;
|
||||||
|
for(;;) {
|
||||||
|
int ok = waitpid(pid, &status, WNOHANG);
|
||||||
|
if (ok >= 0) break;
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
NSLog(@"waitpid(%d): %s", pid, strerror(errno));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
auto iter = find_pid(_table, pid);
|
||||||
|
if (iter == _table.end()) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (iter->fd >= 0) {
|
||||||
|
|
||||||
|
// check for pending i/o ?
|
||||||
|
|
||||||
|
if (iter->controller)
|
||||||
|
read(iter->fd, iter->controller);
|
||||||
|
NSLog(@"Closing fd %d (child exited)", iter->fd);
|
||||||
|
close(iter->fd);
|
||||||
|
iter->fd = -1;
|
||||||
|
}
|
||||||
|
[iter->controller childFinished: status];
|
||||||
|
|
||||||
|
[iter->controller release];
|
||||||
|
iter->controller = nil;
|
||||||
|
*iter = std::move(_table.back());
|
||||||
|
_table.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
NSLog(@"Child %d finished", pid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -25,10 +25,6 @@
|
||||||
|
|
||||||
NSObject <Emulator> *_emulator;
|
NSObject <Emulator> *_emulator;
|
||||||
|
|
||||||
NSThread * _thread;
|
|
||||||
int _fd;
|
|
||||||
std::atomic<pid_t> _pid;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@property (nonatomic, retain) NSDictionary *parameters;
|
@property (nonatomic, retain) NSDictionary *parameters;
|
||||||
|
@ -39,5 +35,7 @@
|
||||||
@property (nonatomic, retain) NSObject<Emulator> *emulator;
|
@property (nonatomic, retain) NSObject<Emulator> *emulator;
|
||||||
|
|
||||||
-(void)initPTY;
|
-(void)initPTY;
|
||||||
|
-(void)childFinished: (int)status;
|
||||||
|
-(void)processData: (const void *)buffer size: (size_t)size;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ChildMonitor.h"
|
||||||
|
|
||||||
@implementation TermWindowController
|
@implementation TermWindowController
|
||||||
|
|
||||||
@synthesize emulator = _emulator;
|
@synthesize emulator = _emulator;
|
||||||
|
@ -46,12 +48,13 @@
|
||||||
|
|
||||||
-(void)dealloc
|
-(void)dealloc
|
||||||
{
|
{
|
||||||
|
[[ChildMonitor monitor] removeController: self];
|
||||||
|
|
||||||
[_emulator release];
|
[_emulator release];
|
||||||
[_emulatorView release];
|
[_emulatorView release];
|
||||||
[_colorView release];
|
[_colorView release];
|
||||||
|
|
||||||
[_parameters release];
|
[_parameters release];
|
||||||
[_thread release];
|
|
||||||
|
|
||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
@ -66,6 +69,9 @@
|
||||||
-(void)initPTY
|
-(void)initPTY
|
||||||
{
|
{
|
||||||
static std::string username;
|
static std::string username;
|
||||||
|
|
||||||
|
pid_t pid;
|
||||||
|
int fd;
|
||||||
|
|
||||||
struct termios term;
|
struct termios term;
|
||||||
struct winsize ws = [_emulator defaultSize];
|
struct winsize ws = [_emulator defaultSize];
|
||||||
|
@ -94,16 +100,16 @@
|
||||||
username = [NSUserName() UTF8String];
|
username = [NSUserName() UTF8String];
|
||||||
}
|
}
|
||||||
//NSLog(@"%@ %s %s", NSUserName(), getlogin(), getpwent()->pw_name);
|
//NSLog(@"%@ %s %s", NSUserName(), getlogin(), getpwent()->pw_name);
|
||||||
_pid = forkpty(&_fd, NULL, &term, &ws);
|
pid = forkpty(&fd, NULL, &term, &ws);
|
||||||
|
|
||||||
if (_pid < 0)
|
if (pid < 0)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "forkpty failed\n");
|
fprintf(stderr, "forkpty failed\n");
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_pid == 0)
|
if (pid == 0)
|
||||||
{
|
{
|
||||||
|
|
||||||
std::vector<const char *> environ;
|
std::vector<const char *> environ;
|
||||||
|
@ -175,122 +181,18 @@
|
||||||
|
|
||||||
[window setMinSize: [window frame].size];
|
[window setMinSize: [window frame].size];
|
||||||
|
|
||||||
[_emulatorView setFd: _fd];
|
[_emulatorView setFd: fd];
|
||||||
[self monitor];
|
|
||||||
|
[[ChildMonitor monitor] addController: self pid: pid fd: fd];
|
||||||
}
|
}
|
||||||
|
|
||||||
-(BOOL)read: (int)fd {
|
|
||||||
|
|
||||||
BOOL rv = NO;
|
-(void)childFinished: (int)status {
|
||||||
|
[_emulatorView childFinished: status];
|
||||||
for(;;) {
|
|
||||||
|
|
||||||
uint8_t buffer[1024];
|
|
||||||
ssize_t size = read(fd, buffer, sizeof(buffer));
|
|
||||||
if (size < 0 && errno == EINTR) continue;
|
|
||||||
|
|
||||||
if (size <= 0) break;
|
|
||||||
[_emulatorView processData: buffer size: size];
|
|
||||||
rv = YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rv;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-(int)wait: (pid_t)pid {
|
-(void)processData: (const void *)buffer size: (size_t)size {
|
||||||
|
[_emulatorView processData: (uint8_t *)buffer size: size];
|
||||||
std::atomic_exchange(&_pid, -1);
|
|
||||||
|
|
||||||
int status = 0;
|
|
||||||
for(;;) {
|
|
||||||
int ok = waitpid(pid, &status, WNOHANG);
|
|
||||||
if (ok >= 0) break;
|
|
||||||
if (errno == EINTR) continue;
|
|
||||||
NSLog(@"waitpid(%d): %s", pid, strerror(errno));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)monitor {
|
|
||||||
|
|
||||||
|
|
||||||
int fd = _fd;
|
|
||||||
pid_t pid = _pid;
|
|
||||||
|
|
||||||
int q = kqueue();
|
|
||||||
|
|
||||||
struct kevent events[2] = {};
|
|
||||||
|
|
||||||
EV_SET(&events[0], pid, EVFILT_PROC, EV_ADD | EV_RECEIPT, NOTE_EXIT | NOTE_EXITSTATUS, 0, NULL);
|
|
||||||
EV_SET(&events[1], fd, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, NULL);
|
|
||||||
|
|
||||||
int flags;
|
|
||||||
// non-blocking io.
|
|
||||||
if (fcntl(_fd, F_GETFL, &flags) < 0) flags = 0;
|
|
||||||
fcntl(_fd, F_SETFL, flags | O_NONBLOCK);
|
|
||||||
|
|
||||||
kevent(q, events, 2, NULL, 0, NULL);
|
|
||||||
|
|
||||||
[_emulatorView childBegan];
|
|
||||||
|
|
||||||
_thread = [[NSThread alloc] initWithBlock: ^(){
|
|
||||||
|
|
||||||
struct kevent events[2] = {};
|
|
||||||
|
|
||||||
bool stop = false;
|
|
||||||
int status = 0;
|
|
||||||
|
|
||||||
while (!stop) {
|
|
||||||
|
|
||||||
int n = kevent(q, NULL, 0, events, 2, NULL);
|
|
||||||
if (n <= 0) {
|
|
||||||
NSLog(@"kevent");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (unsigned i = 0; i < n; ++i) {
|
|
||||||
const auto &e = events[i];
|
|
||||||
unsigned flags = e.flags;
|
|
||||||
if (e.filter == EVFILT_READ) {
|
|
||||||
int fd = (int)e.ident;
|
|
||||||
if (flags & EV_EOF) {
|
|
||||||
NSLog(@"EV_EOF");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags & EV_ERROR) {
|
|
||||||
NSLog(@"EV_ERROR");
|
|
||||||
}
|
|
||||||
|
|
||||||
[self read: fd];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.filter == EVFILT_PROC) {
|
|
||||||
|
|
||||||
pid_t pid = (pid_t)e.ident;
|
|
||||||
NSLog(@"Child finished");
|
|
||||||
status = [self wait: pid];
|
|
||||||
stop = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (![_thread isCancelled]) {
|
|
||||||
|
|
||||||
// read any lingering io...
|
|
||||||
[self read: fd];
|
|
||||||
|
|
||||||
[_emulatorView childFinished: status];
|
|
||||||
}
|
|
||||||
close(q);
|
|
||||||
close(fd);
|
|
||||||
|
|
||||||
_fd = -1;
|
|
||||||
//NSLog(@"Closing fd");
|
|
||||||
}];
|
|
||||||
|
|
||||||
[_thread start];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
|
@ -359,14 +261,7 @@
|
||||||
|
|
||||||
-(void)windowWillClose:(NSNotification *)notification
|
-(void)windowWillClose:(NSNotification *)notification
|
||||||
{
|
{
|
||||||
|
[[ChildMonitor monitor] removeController: self];
|
||||||
pid_t pid = std::atomic_exchange(&_pid, -1);
|
|
||||||
[_thread cancel];
|
|
||||||
|
|
||||||
if (pid > 0) {
|
|
||||||
kill(pid, 9);
|
|
||||||
}
|
|
||||||
|
|
||||||
[self autorelease];
|
[self autorelease];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user