Project

General

Profile

Bug #11271

server-side streams and files with frames/redirected terminal

Added by Constantin Asofiei 5 months ago. Updated 2 months ago.

Status:
Test
Priority:
Normal
Target version:
-
Start date:
Due date:
% Done:

100%

billable:
No
vendor_id:
GCD
case_num:
version_reported:
version_resolved:
reviewer:
production:
No
env_name:
topics:

start.p Magnifier (2.99 KB) Alexandru Lungu, 03/04/2026 06:28 AM

gen_bmp.py Magnifier (4.41 KB) Alexandru Lungu, 03/04/2026 06:30 AM


Related issues

Related to Runtime Infrastructure - Feature #11371: full server-side support of FWD clients New

History

#1 Updated by Constantin Asofiei 5 months ago

Currently, enabling server-side streams does not allow work with FRAMEs (i.e. redirected terminal). To make it work, this requires for FWD client to 'call back' to the FWD server to access the stream. I have some changes which I will commit to 11271a once I have them stable; the downside for this is that the FWD client has granular stream API calls, which in some cases may increase the network trips.

This also affects process streams, which will be on the server.

Alexandru: do you have some standalone tests for the behavior in #11184?

#2 Updated by Alexandru Lungu 5 months ago

Alexandru: do you have some standalone tests for the behavior in #11184?

Attached example run in Hotel.

  • Project needs directory.xml changes.
  • FWD requires some changes from #11184-6 (first patch), the second patch is not final - is what I thought is the closest to something working, but it is not :)

#3 Updated by Alexandru Lungu 5 months ago

AH, it also requires a folder images/ with 100 generated bmps. Attached a script to generate them.

#6 Updated by Constantin Asofiei 4 months ago

For streams to work server-side, we need to also bring the file-system to the server-side. But OS-GETENV converts in FWD as FileSystemOps.getProperty() and executes via FileSystemDaemon.getProperty - and, if file-system is server-side, then the FWD client's env is not available.

So I need to move OS-GETENV to emit via EnvironmentOps and execute via EnvironmentDaemon.

#7 Updated by Constantin Asofiei 4 months ago

  • Assignee set to Constantin Asofiei
  • Status changed from New to Review
  • reviewer Greg Shah added

Greg, please review 11271b rev 16483. I'm still going through testing. Thing to note is launcher can't be used on server-side easily (it requires terminal), but I need to finish testing full PASOE support.

Some notes on the implementation: both a network and a local instance of StreamDaemon is created, to allow bi-directional communication. These instances are wrapped inside a proxy (with the RemoteStreamHandler handler) which decides when and which streams to be moved from the remote side to the local side. So, if a stream uses UI and now is on the server, the client will first 'check it out' from the server (which flushes and closes it) and 'checks it on' on the client (where it gets opened and positioned correctly). The same approach is on the server-side - if the stream is on the client, access is moved to the server by close/open.

The implementation requires both streams and file-system to be on server-side - on a second thought, I think we need to force streams are on server-side if file-system is on server-side (and vice-versa).

Some metrics for this simple test with 100k iterations:

def var ch as char.
def var i as int.
def var n1 as int64.
def var n2 as int64.

n1 = mtime.
form ch with frame f1 side-labels.
def stream rpt.
output stream rpt to a.txt.
do i = 1 to 100000:
   ch = string(i).
   display stream rpt ch with frame f1.
   down stream rpt with frame f1.

   put stream rpt unformatted ch skip.
end.

output stream rpt close.
n2 = mtime.

With server-side streams:
  • display + put: 24390ms
  • only put: 76ms
  • only display: 7662ms
With client-side streams:
  • display + put: 11784ms
  • only put: 4298ms
  • only display: 7503ms

The complex DISPLAY+PUT takes ~2 times more unfortunately, when executed with server-side streams. This is because each iteration requires the stream to be moved from one side to the other a couple of times. I'll look to find if I can improve this.

#8 Updated by Constantin Asofiei 4 months ago

Alex, I've ran your image load test and I don't see any difference with server-side file-system - it takes ~0.5s in both cases. The latest changes are in 11271b rev 16485 (had to fix some other stuff related to server-side file-system, for APIs which are still called from client).

#9 Updated by Constantin Asofiei 4 months ago

Constantin Asofiei wrote:

Alex, I've ran your image load test and I don't see any difference with server-side file-system - it takes ~0.5s in both cases. The latest changes are in 11271b rev 16485 (had to fix some other stuff related to server-side file-system, for APIs which are still called from client).

I've done some debugging now and there is something lets say 'out of order': ButtonGuiImpl.loadImage creates a java FileInputStream - so this does not use 'server-side' file-system or streams, even if enabled. I've tested in Swing for now, I'll check also in Web GUI.

#10 Updated by Constantin Asofiei 4 months ago

Tested also in Web GUI, times are similar to Swing, in both cases.

#11 Updated by Constantin Asofiei 4 months ago

  • % Done changed from 0 to 100

#12 Updated by Greg Shah 4 months ago

  • reviewer Hynek Cihlar added

Hynek: Please review this with urgency.

I will review it shortly.

#13 Updated by Greg Shah 4 months ago

Code Review Task Branch 11271a Revision 16469

1. Let's find a different name than IStream for the interface. I like the changes to abstract that, but the name is too dotnet. Perhaps BaseStream or something like that could be better?

2. The StreamDaemon on the server side is a singleton, which means we must synchronize all of the state access and modification. Please make the class fully thread-safe.

#14 Updated by Greg Shah 4 months ago

Is the testcase from #11271-7 a common pattern? Should we just complete the implementation of a server-side headless/redirected UI?

Hynek: What is the work effort to accomplish that?

#15 Updated by Constantin Asofiei 4 months ago

Greg Shah wrote:

Code Review Task Branch 11271a Revision 16469

Sorry, the branch to review is 11271b rev 16492 as of now.

1. Let's find a different name than IStream for the interface. I like the changes to abstract that, but the name is too dotnet. Perhaps BaseStream or something like that could be better?

In 11271a, yes, I tried to extract APIs which need to be executed by the client-side. But this is not used in 11271b. This needs to be further refactored at some point, to know exactly which APIs are executed by the client and server side.

2. The StreamDaemon on the server side is a singleton, which means we must synchronize all of the state access and modification. Please make the class fully thread-safe.

In 11271b there is no state in StreamDaemon when on server-side: resolveWorkArea is overridden in ServerStreamDaemon to be context-local. ServerSideTerminal is also context-local.

#16 Updated by Hynek Cihlar 4 months ago

Code review 11271b.

FileStream.restoreStateAndOpen always reopens with append=true. What if the original stream is opened with append=false?

resolveId will NPE on unknown streams — StreamDaemon.java

The new resolveId method returns primitive int, but wa.ids.get(stream) returns Integer which can be null. The original code in reverseLookup safely handled null via Integer id = ids.get(s).

Swapped Javadoc on serialization methods — Stream.java and FileStream.java

In both classes, the Javadoc for readExternal (diff lines 1017, 617) says "Serialize this stream's state" when it actually deserializes/restores. Conversely, writeExternal (diff lines 1079, 655) says "Restore this stream's state" when it actually serializes. The comments are transposed.

Should access to RemoteStreamHandler.remoteStreams be synchronized?

Double close in RemoteStreamHandler.invoke for file resource streams

When streamClose is true and the stream is a local file resource, the code:
  1. Calls local.checkOutStream(id) (closes local file handle)
  2. Calls remote.checkInStream(id, state) (opens on remote)
  3. Calls method.invoke(remote, args) (close on remote)
  4. Then falls through to method.invoke(handler, args) where handler local — executing close logic again on the already-checked-out local stream

FileSystemDaemon.singleton used without null guard in static methods.

RuntimeException wrapping in restoreStateAndOpen/saveStateAndClose bypasses 4GL error condition handling. Both methods catch IOException/ClassNotFoundException and wrap them in raw RuntimeException (diff lines 377–385, 417–419). If a file cannot be re-opened during stream migration (e.g., file deleted between check-out and check-in), this RuntimeException will propagate through the block execution stack. BlockManager.exceptionToCondition() does not map plain RuntimeException to any 4GL condition — it will be treated as an unhandled exception, likely surfacing as a STOP condition rather than an ERROR condition that ON ERROR phrases could handle.

sourceCharset can become null after deserialization
Stream.readExternal sets sourceCharset = javaSourceCS null ? null : Charset.forName(javaSourceCS). But the field's Javadoc says "This is never null".

I think "store" in RemoteStreamHelper should advance next stream id to be in sync with StreamDaemon.store().

#17 Updated by Constantin Asofiei 4 months ago

Hynek Cihlar wrote:

Code review 11271b.

FileStream.restoreStateAndOpen always reopens with append=true. What if the original stream is opened with append=false?

Once the stream is opened, we are always appending - we can't reset its content.

The new resolveId method returns primitive int, but wa.ids.get(stream) returns Integer which can be null. The original code in reverseLookup safely handled null via Integer id = ids.get(s).

Fixed

Swapped Javadoc on serialization methods — Stream.java and FileStream.java

Fixed

Should access to RemoteStreamHandler.remoteStreams be synchronized?

No, ServerResourceManager is context-local thus the proxy using RemoteStreamHandler is context-local

Double close in RemoteStreamHandler.invoke for file resource streams

When streamClose is true and the stream is a local file resource, the code:
  1. Calls local.checkOutStream(id) (closes local file handle)
  2. Calls remote.checkInStream(id, state) (opens on remote)
  3. Calls method.invoke(remote, args) (close on remote)
  4. Then falls through to method.invoke(handler, args) where handler local — executing close logic again on the already-checked-out local stream

Well, this close logic will also remove the stream from the various registries. That's why I use this order: checkout/checkin doesn't affect the registries, and calling close on both remote and local ensures both sides have cleared everything.

FileSystemDaemon.singleton used without null guard in static methods.

We have a case in deleteWorker where a JVM shutdown hook is executed - see #11325, but otherwise this must never be null.

RuntimeException wrapping in restoreStateAndOpen/saveStateAndClose bypasses 4GL error condition handling. Both methods catch IOException/ClassNotFoundException and wrap them in raw RuntimeException (diff lines 377–385, 417–419). If a file cannot be re-opened during stream migration (e.g., file deleted between check-out and check-in), this RuntimeException will propagate through the block execution stack. BlockManager.exceptionToCondition() does not map plain RuntimeException to any 4GL condition — it will be treated as an unhandled exception, likely surfacing as a STOP condition rather than an ERROR condition that ON ERROR phrases could handle.

This must never happen. I'd rather abend if 'the OS did something with the file during switch' than treat this as a 4GL condition.

sourceCharset can become null after deserialization
Stream.readExternal sets sourceCharset = javaSourceCS null ? null : Charset.forName(javaSourceCS). But the field's Javadoc says "This is never null".

This is just a protection, it should never happen.

I think "store" in RemoteStreamHelper should advance next stream id to be in sync with StreamDaemon.store().

I need to double-check this.

#18 Updated by Greg Shah 4 months ago

  • reviewer deleted (Greg Shah)

Code Review Task Branch 11271a Revision 16469

Sorry, the branch to review is 11271b rev 16492 as of now.

No problem. I'm going to defer to Hynek's much more extansive review.

#19 Updated by Teodor Gorghe 4 months ago

Constantin, do you need to do a change in 11271b? I am asking about this to know if I put this into ChUI regression testing.

#20 Updated by Constantin Asofiei 4 months ago

Teodor Gorghe wrote:

Constantin, do you need to do a change in 11271b? I am asking about this to know if I put this into ChUI regression testing.

Please go ahead and run ChUI.

#21 Updated by Constantin Asofiei 4 months ago

Hynek, you were correct to fix the 'nextId' on 'store'. Also I found a regression in ChUI - RemoteStream was caching state which was not set properly when opening a process stream. The fixes are in 11271b rev 16502 (after rebase).

I'll run ChUI, ETF and SXe again.

Greg: I think we need to plan for server-side redirected terminal (and thus frames) rather sooner than later; I think for batch processes, we can use this.

#22 Updated by Greg Shah 4 months ago

I think we need to plan for server-side redirected terminal (and thus frames) rather sooner than later; I think for batch processes, we can use this.

I agree. The question from #11271-14 remains: what is the expected effort for that?

#23 Updated by Hynek Cihlar 4 months ago

Constantin Asofiei wrote:

Hynek, you were correct to fix the 'nextId' on 'store'.

Actually this one was spotted with AI ;-).

#24 Updated by Constantin Asofiei 4 months ago

  • Status changed from Review to Test

Branch 11271b was merged to trunk rev 16508 and archived.

#25 Updated by Constantin Asofiei 4 months ago

Greg Shah wrote:

I think we need to plan for server-side redirected terminal (and thus frames) rather sooner than later; I think for batch processes, we can use this.

I agree. The question from #11271-14 remains: what is the expected effort for that?

There are not many static fields in the p2j.ui package remaining. After we clean this up and make them context-local, what we need to do is run a FWD client only on server-side; I would like to do this still via ClientDriver, which after authentication, if it sees server-side-resources=ALL, it switches automatically to this 'headless' mode (i.e. closes the socket, etc) and initializes ThinClient and other needed UI resources on server-side.

As for the effort: I think 3-4 weeks of work.

#26 Updated by Greg Shah 4 months ago

Constantin Asofiei wrote:

Greg Shah wrote:

I think we need to plan for server-side redirected terminal (and thus frames) rather sooner than later; I think for batch processes, we can use this.

I agree. The question from #11271-14 remains: what is the expected effort for that?

There are not many static fields in the p2j.ui package remaining. After we clean this up and make them context-local, what we need to do is run a FWD client only on server-side; I would like to do this still via ClientDriver, which after authentication, if it sees server-side-resources=ALL, it switches automatically to this 'headless' mode (i.e. closes the socket, etc) and initializes ThinClient and other needed UI resources on server-side.

As for the effort: I think 3-4 weeks of work.

Please create a new task for this.

#27 Updated by Constantin Asofiei 3 months ago

  • Related to Feature #11371: full server-side support of FWD clients added

#28 Updated by Constantin Asofiei 3 months ago

Branch 11271c was created from trunk rev 16510. In rev 16511 it contains:
  • If any context does not have ALL server-side resources, then register the network server in 'initializeServer', for filesystem and stream resources. Refs #11271 #11259

I'll merge this now. Affects multisession FWD mode.

#29 Updated by Constantin Asofiei 3 months ago

Branch 11271c was merged to trunk rev 16511 and archived.

#30 Updated by Hynek Cihlar 3 months ago

Greg Shah wrote:

Is the testcase from #11271-7 a common pattern? Should we just complete the implementation of a server-side headless/redirected UI?

Hynek: What is the work effort to accomplish that?

See #9055 and 9055a where the initial support for running client-side UI in server context was implemented. It included very limited use cases like MESSAGE statement.

For full UI I suppose the UI code will have to be cleaned from static Java fields and proper use of security context. A rough estimate 15 MDs.

#31 Updated by Constantin Asofiei 3 months ago

Branch 11271d fixes a conversion problem: FileSystemDaemon is used in conversion by .xpxg analysis (for OpenClient). This was merged to trunk rev 16516

#33 Updated by Alexandru Lungu 2 months ago

Constantin, is there any reason for:

      if (wa.serverSideFs && !Paths.get(filename).isAbsolute())
      {
         if (filename.startsWith("." + getFileSeparator()))
         {
            filename = filename.substring(2);
         }
         filename = getWorkingDirectory().value.concat(getFileSeparator()).concat(filename);
      }

inside FileSystemOps.searchPath? This class does:

  • lookupLegacyName which should use relative names like my/project/proc.p. It won't work with absolute path and/or getWorkingDirectory prefixed.
  • ignoreClientSearch honoring that, at least in another project, has my/project/proc.p relative paths.
  • searchResourceJars that searches a file in a jar, but relative to the root of the jar. Also, appending getWorkingDirectory doesn't help at all.
  • if (filename.equalsIgnoreCase(quoterExecName)) won't ever match.

So this code basically makes FileSystemOps.searchPath a no-op. Am I misunderstanding something?

#34 Updated by Constantin Asofiei 2 months ago

Alexandru Lungu wrote:

So this code basically makes FileSystemOps.searchPath a no-op. Am I misunderstanding something?

Alex, you are correct; this needs to be moved lower in the method, after 4GL and jar lookup.

#35 Updated by Alexandru Lungu 2 months ago

I have some changes; I will commit them by EOD in 11242a where I tried to make filesystem,stream as a server-side resource work for the customer there.

Also available in: Atom PDF