@@ -139,11 +139,14 @@ func (s *JuliaSession) isAlive() bool {
139139}
140140
141141type readResult struct {
142- lines [] string
143- err error
142+ output string
143+ err error
144144}
145145
146146func (s * JuliaSession ) executeRaw (code string , timeoutSecs float64 ) (string , error ) {
147+ // The sentinel command writes an extra "\n" before the marker so it always
148+ // starts on its own line even when the user code didn't end with a newline.
149+ // We strip exactly that one "\n" when assembling the result.
147150 sentinelCmd := fmt .Sprintf (
148151 "flush(stderr); write(stdout, \" \\ n\" ); println(stdout, \" %s\" ); flush(stdout)\n " ,
149152 s .sentinel ,
@@ -154,46 +157,46 @@ func (s *JuliaSession) executeRaw(code string, timeoutSecs float64) (string, err
154157
155158 ch := make (chan readResult , 1 )
156159 go func () {
157- var lines [] string
160+ var buf strings. Builder
158161 for {
159162 line , err := s .stdout .ReadString ('\n' )
160- trimmed := strings .TrimRight (line , "\r \n " )
161- if trimmed == s .sentinel {
162- if len (lines ) > 0 && lines [len (lines )- 1 ] == "" {
163- lines = lines [:len (lines )- 1 ]
163+ if strings .TrimRight (line , "\r \n " ) == s .sentinel {
164+ // Strip the one "\n" we injected before the sentinel.
165+ out := buf .String ()
166+ if len (out ) > 0 && out [len (out )- 1 ] == '\n' {
167+ out = out [:len (out )- 1 ]
164168 }
165- ch <- readResult {lines , nil }
169+ ch <- readResult {out , nil }
166170 return
167171 }
168172 if err != nil {
169173 s .dead .Store (true )
170- ch <- readResult {lines , fmt .Errorf ("Julia process died during execution.\n Output before death:\n %s" , strings . Join ( lines , " \n " ))}
174+ ch <- readResult {buf . String () , fmt .Errorf ("Julia process died during execution.\n Output before death:\n %s" , buf . String ( ))}
171175 return
172176 }
173- lines = append ( lines , trimmed )
177+ buf . WriteString ( line )
174178 }
175179 }()
176180
177181 if timeoutSecs <= 0 {
178182 r := <- ch
179- return strings . Join ( r . lines , " \n " ) , r .err
183+ return r . output , r .err
180184 }
181185
182186 timer := time .NewTimer (time .Duration (float64 (time .Second ) * timeoutSecs ))
183187 defer timer .Stop ()
184188
185189 select {
186190 case r := <- ch :
187- return strings . Join ( r . lines , " \n " ) , r .err
191+ return r . output , r .err
188192 case <- timer .C :
189193 s .proc .Process .Kill ()
190194 s .proc .Wait ()
191195 s .dead .Store (true )
192196 r := <- ch // goroutine unblocks on EOF after kill
193- partial := strings .Join (r .lines , "\n " )
194197 msg := fmt .Sprintf ("Execution timed out after %vs. Session killed; it will restart on next call." , timeoutSecs )
195- if partial != "" {
196- msg += "\n \n Output before timeout:\n " + partial
198+ if r . output != "" {
199+ msg += "\n \n Output before timeout:\n " + r . output
197200 }
198201 return "" , fmt .Errorf ("%s" , msg )
199202 }
0 commit comments