singrdk/base/Applications/Runtime/Singularity/Io/PipeLookAhead.sg

403 lines
14 KiB
Plaintext
Raw Normal View History

2008-03-05 09:52:00 -05:00
////////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: PipeLookAhead.sg
//
// Note: Implements a selectable abstraction that looks ahead on a pipe channels
// for control-C and exports separate logical receivable messages for
// non-control-C characters and control-C, effectively allowing one to
// consume Ctrl-C's out of order.
//
using System;
using Microsoft.SingSharp;
using Microsoft.Singularity;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity.V1.Threads;
using Tty = Microsoft.Singularity.Io.Tty2006;
namespace Microsoft.Singularity.Io {
public sealed class PipeLookAhead : ITracked, ISelectable {
private struct CircularBuffer : ITracked {
private char[]! in ExHeap buffer; // circular buffer
private int nextReadIndex;
private int nextWriteIndex;
private int controlC_Count; // ctrl-c's in buffer
private int controlC_Consumed; // ctrl-c's consumed in buffer
private int controlZ_Count; // ctrl-z's in buffer
private int controlZ_Consumed; // ctrl-z's consumed in buffer
public bool HasControlC {
get { return controlC_Count > 0; }
}
public bool HasControlZ {
get { return controlZ_Count > 0; }
}
private int CharsInBuffer {
get {
if (nextWriteIndex >= nextReadIndex) {
return nextWriteIndex - nextReadIndex;
}
else {
expose (this) {
return buffer.Length - nextReadIndex + nextWriteIndex;
}
}
}
}
public int CharsAvailable {
get {
int available = CharsInBuffer - controlC_Consumed - controlZ_Consumed;
return available;
}
}
public CircularBuffer(int lookAhead) {
this.buffer = new[ExHeap] char[lookAhead+256];
this.nextReadIndex = 0;
this.nextWriteIndex = 0;
this.controlC_Count = 0;
this.controlC_Consumed = 0;
this.controlZ_Count = 0;
this.controlZ_Consumed = 0;
}
private int FreeSpace {
get {
// Careful, circular buffer can't be filled completely.
// Need 1 extra space to distinguish full from empty buffer.
expose (this) {
return this.buffer.Length - this.CharsInBuffer - 1;
}
}
}
public void Add(char[]! in ExHeap buffer, int index, int count) {
EnsureCapacity(count);
for (int i=0; i<count; i++) {
Add(buffer[index++]);
}
}
private void Add(char c) {
expose (this) {
this.buffer[this.nextWriteIndex++] = c;
if (this.nextWriteIndex == this.buffer.Length) {
this.nextWriteIndex = 0; // wrap around
}
if (c == (int)Tty.ControlCodes.CTRL_C) {
this.controlC_Count++;
}
if (c == (int)Tty.ControlCodes.CTRL_Z) {
this.controlZ_Count++;
}
/*
DebugStub.WriteLine("Add {0} writeIndex={1} readIndex={2}",
__arglist(c, this.nextWriteIndex,
this.nextReadIndex));
*/
}
}
private void EnsureCapacity(int count) {
int freeSpace = this.FreeSpace;
int charsBuffered = this.CharsInBuffer;
if (freeSpace < count) {
expose (this) {
int newSize = this.buffer.Length + count;
char[] in ExHeap newBuffer = new[ExHeap] char[newSize];
char[] in ExHeap oldBuffer = this.buffer;
if (this.nextWriteIndex >= this.nextReadIndex) {
// copy area between indices
Bitter.Copy(newBuffer, 0, charsBuffered,
oldBuffer, this.nextReadIndex);
}
else {
// copy areas from read to end and from start to write.
int suffix = this.buffer.Length - this.nextReadIndex;
Bitter.Copy(newBuffer, 0, suffix,
oldBuffer, this.nextReadIndex);
Bitter.Copy(newBuffer, suffix, this.nextWriteIndex,
oldBuffer, 0);
}
delete this.buffer;
this.buffer = newBuffer;
this.nextReadIndex = 0;
this.nextWriteIndex = charsBuffered;
}
}
}
public void Dispose()
{
delete this.buffer;
}
void ITracked.Acquire() {}
void ITracked.Release() {}
void ITracked.Expose() {}
void ITracked.UnExpose() {}
public void GetChar(out char ch) {
expose (this) {
tryAgain:
assert CharsAvailable > 0;
ch = this.buffer[this.nextReadIndex++];
if (this.nextReadIndex == this.buffer.Length) {
this.nextReadIndex = 0; // wrap around
}
if (ch == (int)Tty.ControlCodes.CTRL_C) {
if (this.controlC_Consumed > 0) {
// filter. already consumed via Ctrl_C
this.controlC_Consumed--;
goto tryAgain;
}
else {
this.controlC_Count--;
}
}
if (ch == (int)Tty.ControlCodes.CTRL_Z) {
if (this.controlZ_Consumed > 0) {
// filter. already consumed via Ctrl_Z
this.controlZ_Consumed--;
goto tryAgain;
}
else {
this.controlZ_Count--;
}
}
/*
char c = ch;
DebugStub.WriteLine("GetChar {0} writeIndex={1} readIndex={2} CtrlC={3} CtrlZ={4} C-Used={5} Z-Used={6}",
__arglist(c, this.nextWriteIndex,
this.nextReadIndex,
this.controlC_Count,
this.controlZ_Count,
this.controlC_Consumed,
this.controlZ_Consumed
));
*/
}
}
public void ConsumeControlC() {
assert this.controlC_Count > 0;
this.controlC_Count--;
this.controlC_Consumed++;
}
public void ConsumeControlZ() {
assert this.controlZ_Count > 0;
this.controlZ_Count--;
this.controlZ_Consumed++;
}
}
private UnicodePipeContract.Exp pipe; // null when pipe is closed
private int lookAhead;
private CircularBuffer buffer;
public PipeLookAhead([Claims] UnicodePipeContract.Exp pipe, int lookAhead)
{
this.pipe = pipe;
this.lookAhead = lookAhead;
this.buffer = new CircularBuffer(lookAhead);
}
public void Dispose()
{
this.buffer.Dispose();
delete this.pipe;
}
void ITracked.Acquire() {}
void ITracked.Release() {}
void ITracked.Expose() {}
void ITracked.UnExpose() {}
SyncHandle ISelectable.GetWaitHandle() {
expose (this) {
// non-null checker was right in pointing out issue if pipe is null.
// Design problem: what handle do we return in this case?
if (this.pipe != null) {
return this.pipe.GetWaitHandle();
}
else {
DebugStub.Break();
return new SyncHandle(); // null handle. Could cause null-deref
// should we make kernel tolerate this?
}
}
}
void ISelectable.ResetWaitSignal() {
// dummy
}
private void FillToLookAheadWithoutBlock()
{
expose (this) {
UnicodePipeContract.Exp pipe = this.pipe;
if (pipe == null) return;
while (this.buffer.CharsAvailable < this.lookAhead)
{
switch receive {
case pipe.Write(buffer,index,count):
this.buffer.Add(buffer, index, count);
pipe.SendAckWrite(buffer);
break;
case pipe.ChannelClosed():
delete pipe;
this.pipe = null;
return;
case timeout:
return;
}
}
} //expose
}
private bool IsClosed {
get {
expose(this) {
return (this.pipe == null && this.buffer.CharsAvailable==0);
}
}
}
private bool HasCharacter {
get {
expose(this) {
return this.buffer.CharsAvailable > 0;
}
}
}
private int CharsAvailable {
get {
expose(this) {
return this.buffer.CharsAvailable;
}
}
}
private bool HasControlC {
get {
expose(this) {
return this.buffer.HasControlC;
}
}
}
private bool HasControlZ {
get {
expose(this) {
return this.buffer.HasControlZ;
}
}
}
enum SelectTag {
ChannelClosed = -1,
Any = 1,
Char = 2,
ControlC = 3,
ControlZ = 4,
}
/// <summary>
/// blocks until a character is available
/// </summary>
public int ReadChar() {
PipeLookAhead pipe = this;
switch receive {
case pipe.Char(ch):
return ch;
case pipe.ChannelClosed():
return -1;
}
}
public bool HeadMatches(int tag, ref bool possible, ref object setMatch) {
this.FillToLookAheadWithoutBlock();
switch ((SelectTag)tag) {
case SelectTag.ChannelClosed:
if (this.IsClosed) {
return true;
}
if (this.HasCharacter) {
possible = false;
}
return false;
case SelectTag.Char:
if (this.HasCharacter) {
return true;
}
if (this.IsClosed) {
possible = false;
}
return false;
case SelectTag.ControlC:
if (this.HasControlC) {
return true;
}
if (this.IsClosed) {
possible = false;
}
return false;
case SelectTag.ControlZ:
if (this.HasControlZ) {
return true;
}
if (this.IsClosed) {
possible = false;
}
return false;
}
return false;
}
[Selectable((int)SelectTag.Char)]
public void RecvChar(out char ch) {
expose(this) {
this.buffer.GetChar(out ch);
}
}
[Selectable((int)SelectTag.ControlC)]
public void RecvControlC() {
expose(this) {
this.buffer.ConsumeControlC();
}
}
[Selectable((int)SelectTag.ControlZ)]
public void RecvControlZ() {
expose(this) {
this.buffer.ConsumeControlZ();
}
}
}
}