Skip to content

Commit 2a6c8e8

Browse files
ileasilenikitinas
authored andcommitted
Post-review fixes
1 parent 2e128ef commit 2a6c8e8

File tree

7 files changed

+77
-44
lines changed

7 files changed

+77
-44
lines changed

README.md

+3-8
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,9 @@ The following line magics are supported:
9292
- `%use <lib1>, <lib2> ...` - injects code for supported libraries: artifact resolution, default imports, initialization code, type renderers
9393
- `%trackClasspath` - logs any changes of current classpath. Useful for debugging artifact resolution failures
9494
- `%trackExecution` - logs pieces of code that are going to be executed. Useful for debugging of libraries support
95-
- `%output [--max-cell-size=N] [--max-buffer=N] [--max-buffer-newline=N] [--max-time=N] [--no-stdout] [--reset-to-defaults]` -
96-
output capturing settings.
97-
- `max-cell-size` specifies the characters count which may be printed to stdout. Default is 100000.
98-
- `max-buffer` - max characters count stored in internal buffer before being sent to client. Default is 10000.
99-
- `max-buffer-newline` - same as above, but trigger happens only if newline character was encountered. Default is 100.
100-
- `max-time` - max time in milliseconds before the buffer is sent to client. Default is 100.
101-
- `no-stdout` - don't capture output. Default is false.
102-
- `reset-to-defaults` - reset all output settings that were set with magics to defaults
95+
- `%output [options]` - output capturing settings.
96+
97+
See detailed info about line magics [here](doc/magics.md).
10398

10499
### Supported Libraries
105100

doc/magics.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Line magics
2+
The following line magics are supported:
3+
- `%use <lib1>, <lib2> ...` - injects code for supported libraries: artifact resolution, default imports, initialization code, type renderers
4+
- `%trackClasspath` - logs any changes of current classpath. Useful for debugging artifact resolution failures
5+
- `%trackExecution` - logs pieces of code that are going to be executed. Useful for debugging of libraries support
6+
- `%output [--max-cell-size=N] [--max-buffer=N] [--max-buffer-newline=N] [--max-time=N] [--no-stdout] [--reset-to-defaults]` -
7+
output capturing settings.
8+
- `max-cell-size` specifies the characters count which may be printed to stdout. Default is 100000.
9+
- `max-buffer` - max characters count stored in internal buffer before being sent to client. Default is 10000.
10+
- `max-buffer-newline` - same as above, but trigger happens only if newline character was encountered. Default is 100.
11+
- `max-time` - max time in milliseconds before the buffer is sent to client. Default is 100.
12+
- `no-stdout` - don't capture output. Default is false.
13+
- `reset-to-defaults` - reset all output settings that were set with magics to defaults
14+

src/main/kotlin/org/jetbrains/kotlin/jupyter/config.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ enum class JupyterSockets {
4242

4343
data class OutputConfig(
4444
var captureOutput: Boolean = true,
45-
var captureBufferTimeLimitMs: Int = 100,
45+
var captureBufferTimeLimitMs: Long = 100,
4646
var captureBufferMaxSize: Int = 1000,
4747
var cellOutputMaxSize: Int = 100000,
4848
var captureNewlineBufferSize: Int = 100

src/main/kotlin/org/jetbrains/kotlin/jupyter/magics.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.github.ajalt.clikt.parameters.options.default
55
import com.github.ajalt.clikt.parameters.options.flag
66
import com.github.ajalt.clikt.parameters.options.option
77
import com.github.ajalt.clikt.parameters.types.int
8+
import com.github.ajalt.clikt.parameters.types.long
89
import org.jetbrains.kotlin.jupyter.repl.spark.ClassWriter
910

1011
enum class ReplLineMagics(val desc: String, val argumentsUsage: String? = null, val visibleInHelp: Boolean = true) {
@@ -28,7 +29,7 @@ fun processMagics(repl: ReplForJupyter, code: String): String {
2829
val max: Int by option("--max-cell-size", help = "Maximum cell output").int().default(conf.cellOutputMaxSize)
2930
val maxBuffer: Int by option("--max-buffer", help = "Maximum buffer size").int().default(conf.captureBufferMaxSize)
3031
val maxBufferNewline: Int by option("--max-buffer-newline", help = "Maximum buffer size when newline got").int().default(conf.captureNewlineBufferSize)
31-
val maxTimeInterval: Int by option("--max-time", help = "Maximum time wait for output to accumulate").int().default(conf.captureBufferTimeLimitMs)
32+
val maxTimeInterval: Long by option("--max-time", help = "Maximum time wait for output to accumulate").long().default(conf.captureBufferTimeLimitMs)
3233
val dontCaptureStdout: Boolean by option("--no-stdout", help = "Don't capture output").flag(default = !conf.captureOutput)
3334
val reset: Boolean by option("--reset-to-defaults", help = "Reset to defaults").flag()
3435

src/main/kotlin/org/jetbrains/kotlin/jupyter/protocol.kt

+49-25
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import com.beust.klaxon.JsonObject
44
import jupyter.kotlin.DisplayResult
55
import jupyter.kotlin.MimeTypedResult
66
import jupyter.kotlin.textResult
7+
import org.jetbrains.annotations.TestOnly
78
import org.jetbrains.kotlin.config.KotlinCompilerVersion
89
import java.io.ByteArrayOutputStream
910
import java.io.OutputStream
1011
import java.io.PrintStream
1112
import java.lang.reflect.InvocationTargetException
1213
import java.util.concurrent.atomic.AtomicLong
14+
import kotlin.concurrent.timer
1315

1416
enum class ResponseState {
1517
Ok, Error
@@ -177,47 +179,70 @@ class CapturingOutputStream(private val stdout: PrintStream,
177179
private val conf: OutputConfig,
178180
private val captureOutput: Boolean,
179181
val onCaptured: (String) -> Unit) : OutputStream() {
180-
val capturedOutput = ByteArrayOutputStream()
181-
private var time = System.currentTimeMillis()
182+
private val capturedLines = ByteArrayOutputStream()
183+
private val capturedNewLine = ByteArrayOutputStream()
182184
private var overallOutputSize = 0
183185
private var newlineFound = false
184186

185-
private fun shouldSend(b: Int): Boolean {
187+
private val timer = timer(
188+
initialDelay = conf.captureBufferTimeLimitMs,
189+
period = conf.captureBufferTimeLimitMs,
190+
action = {
191+
flush()
192+
})
193+
194+
val contents: ByteArray
195+
@TestOnly
196+
get() = capturedLines.toByteArray() + capturedNewLine.toByteArray()
197+
198+
private fun sendIfNeeded(b: Int) {
186199
val c = b.toChar()
187-
newlineFound = newlineFound || c == '\n' || c == '\r'
188-
if (newlineFound && capturedOutput.size() >= conf.captureNewlineBufferSize)
189-
return true
190-
if (capturedOutput.size() >= conf.captureBufferMaxSize)
191-
return true
192-
193-
val currentTime = System.currentTimeMillis()
194-
if (currentTime - time >= conf.captureBufferTimeLimitMs) {
195-
time = currentTime
196-
return true
200+
if (c == '\n' || c == '\r') {
201+
newlineFound = true
202+
capturedNewLine.writeTo(capturedLines)
203+
capturedNewLine.reset()
197204
}
198-
return false
205+
206+
val size = capturedLines.size() + capturedNewLine.size()
207+
208+
if (newlineFound && size >= conf.captureNewlineBufferSize)
209+
return flushBuffers(capturedLines)
210+
if (size >= conf.captureBufferMaxSize)
211+
return flush()
199212
}
200213

201214
override fun write(b: Int) {
202215
++overallOutputSize
203216
stdout.write(b)
204217

205218
if (captureOutput && overallOutputSize <= conf.cellOutputMaxSize) {
206-
capturedOutput.write(b)
207-
if (shouldSend(b)) {
208-
flush()
209-
}
219+
capturedNewLine.write(b)
220+
sendIfNeeded(b)
210221
}
211222
}
212223

213-
override fun flush() {
224+
private fun resetBuffer(buffer: ByteArrayOutputStream): String {
225+
val str = buffer.toString("UTF-8")
226+
buffer.reset()
227+
return str
228+
}
229+
230+
private fun flushBuffers(vararg buffers: ByteArrayOutputStream) {
214231
newlineFound = false
215-
if (capturedOutput.size() > 0) {
216-
val str = capturedOutput.toString("UTF-8")
217-
capturedOutput.reset()
232+
val str = buffers.map(this::resetBuffer).reduce { acc, s -> acc + s }
233+
if (str.isNotEmpty()) {
218234
onCaptured(str)
219235
}
220236
}
237+
238+
override fun flush() {
239+
flushBuffers(capturedLines, capturedNewLine)
240+
}
241+
242+
override fun close() {
243+
super.close()
244+
timer.cancel()
245+
}
221246
}
222247

223248
fun Any.toMimeTypedResult(): MimeTypedResult? = when (this) {
@@ -301,7 +326,6 @@ fun JupyterConnection.evalWithIO(maybeConfig: OutputConfig?, body: () -> EvalRes
301326
*/
302327
val stdErr = StringBuilder()
303328
with(stdErr) {
304-
forkedError.capturedOutput.toString("UTF-8")?.nullWhenEmpty()?.also { appendln(it) }
305329
val cause = ex.errorResult.cause
306330
if (cause == null) appendln(ex.errorResult.message)
307331
else {
@@ -318,10 +342,10 @@ fun JupyterConnection.evalWithIO(maybeConfig: OutputConfig?, body: () -> EvalRes
318342
ResponseWithMessage(ResponseState.Error, textResult("Error!"), emptyList(), null, stdErr.toString())
319343
}
320344
} finally {
345+
forkedOut.close()
346+
forkedError.close()
321347
System.setIn(`in`)
322348
System.setErr(err)
323349
System.setOut(out)
324350
}
325351
}
326-
327-
fun String.nullWhenEmpty(): String? = if (this.isBlank()) null else this

src/test/kotlin/org/jetbrains/kotlin/jupyter/test/capturingStreamTests.kt

+8-8
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class CapturingStreamTests {
1616

1717
private fun getStream(stdout: OutputStream = nullOStream,
1818
captureOutput: Boolean = true,
19-
maxBufferLifeTimeMs: Int = 1000,
19+
maxBufferLifeTimeMs: Long = 1000,
2020
maxBufferSize: Int = 1000,
2121
maxOutputSize: Int = 1000,
2222
maxBufferNewlineSize: Int = 1,
@@ -37,7 +37,7 @@ class CapturingStreamTests {
3737
fun testMaxOutputSizeError() {
3838
val s = getStream(maxOutputSize = 3)
3939
s.write("java".toByteArray())
40-
assertArrayEquals("jav".toByteArray(), s.capturedOutput.toByteArray())
40+
assertArrayEquals("jav".toByteArray(), s.contents)
4141
}
4242

4343
@Test
@@ -46,11 +46,11 @@ class CapturingStreamTests {
4646

4747
val s1 = getStream(captureOutput = false)
4848
s1.write(contents)
49-
assertEquals(0, s1.capturedOutput.size())
49+
assertEquals(0, s1.contents.size)
5050

5151
val s2 = getStream(captureOutput = true)
5252
s2.write(contents)
53-
assertArrayEquals(contents, s2.capturedOutput.toByteArray())
53+
assertArrayEquals(contents, s2.contents)
5454
}
5555

5656
@Test
@@ -73,7 +73,7 @@ class CapturingStreamTests {
7373
@Test
7474
fun testNewlineBufferSize() {
7575
val contents = "12345\n12\n3451234567890".toByteArray()
76-
val expected = arrayOf("12345\n", "12\n345", "123456789", "0")
76+
val expected = arrayOf("12345\n", "12\n", "345123456", "7890")
7777

7878
var i = 0
7979
val s = getStream(maxBufferSize = 9, maxBufferNewlineSize = 6) {
@@ -89,8 +89,8 @@ class CapturingStreamTests {
8989

9090
@Test
9191
fun testMaxBufferLifeTime() {
92-
val strings = arrayOf("c ", "a", "ada ", "b", "scala ", "c")
93-
val expected = arrayOf("c a", "ada b", "scala c")
92+
val strings = arrayOf("11", "22", "33", "44", "55", "66")
93+
val expected = arrayOf("1122", "3344", "5566")
9494

9595
var i = 0
9696
val s = getStream(maxBufferLifeTimeMs = 1000) {
@@ -99,7 +99,7 @@ class CapturingStreamTests {
9999
}
100100

101101
strings.forEach {
102-
Thread.sleep(600)
102+
Thread.sleep(450)
103103
s.write(it.toByteArray())
104104
}
105105

src/test/kotlin/org/jetbrains/kotlin/jupyter/test/executeTests.kt

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ fun Message.type(): String {
1212

1313
class ExecuteTests : KernelServerTestsBase() {
1414

15-
@Synchronized
1615
private fun doExecute(code : String, hasResult: Boolean = true, ioPubChecker : (ZMQ.Socket) -> Unit = {}) : Any? {
1716
val context = ZMQ.context(1)
1817
val shell = context.socket(ZMQ.REQ)

0 commit comments

Comments
 (0)