Project

General

Profile

Appserver Support

Configuration

Appserver configuration is done in FWD's directory. This includes the appserver details, the FWD process account under which the appserver Agent is executing, and more. This section will describe what configuration is required to setup an appserver in FWD's directory.

In the examples in this section:
  • the appserver is assumed to be named app_server
  • the FWD process account is assumed to be named appserver_process

Add the Process Account

Follow Batch Processes to add a FWD process account in directory.xml. The final definition should look like:

          <node class="process" name="appserver_process">
            <node-attribute name="appserver" value="app_server"/>
            <node-attribute name="enabled" value="TRUE"/>
            <node-attribute name="description" value="Process for appserver."/>
            <node-attribute name="alias" value="appserver_process"/>
            <node-attribute name="master" value="FALSE"/>
            <node-attribute name="server" value="FALSE"/>
          </node>

The certificate must be installed using Cryptography Setup Helper.

Configure the Scheduler

Follow Scheduler to schedule the automatic launch of the app_server at startup. The final configuration should look like this:

        <node class="container" name="scheduler">
          <node class="job" name="start_appserver">
            <node-attribute name="type" value="process"/>
            <node-attribute name="target" value="appserver_process"/>
            <node-attribute name="enabled" value="TRUE"/>
            <node-attribute name="mode" value="now"/>
          </node>
        </node>

Configure the Spawner

Follow Spawner setup to configure the clientConfig node for the FWD process associated with the appserver.

Configure the Appserver

Follow Configuring a Legacy Appserver to configure the appserver details in FWD. This configuration is for launching the appserver Agents, and not for appserver connections. The result should look like this:

        <node class="container" name="appservers">
          <node class="appserver" name="app_server">
            <node-attribute name="operating_mode" value="state-free"/>
            <node-attribute name="request_timeout" value="0"/>
            <node-attribute name="auto_trim_timeout" value="30"/>
            <node-attribute name="propath" value=".:appsrv/api:"/>
            <node-attribute name="initial_agents" value="1"/>
            <node-attribute name="min_agents" value="1"/>
            <node-attribute name="max_agents" value="1"/>
            <node-attribute name="activate" value=""/>
            <node-attribute name="deactivate" value=""/>
            <node-attribute name="connect" value=""/>
            <node-attribute name="disconnect" value=""/>
            <node-attribute name="startup" value=""/>
            <node-attribute name="shutdown" value=""/>
            <node-attribute name="startup_parameter" value=""/>
          </node>
        </node>

Add as many appservers configurations as required, with their configuration usually being extracted from the OpenEdge ubroker.properties files.

Configure the Appserver for Remote Connections

If the appserver is accessed from 4GL code (remote or local for the FWD server), app_services, follow Configuring an Appserver Client to specify the details. The result may look like this:

        <node class="container" name="app_services">
          <node class="container" name="app_server">
            <node class="integer" name="p2j_port">
              <node-attribute name="value" value="3333"/>
            </node>
            <node class="string" name="p2j_host">
              <node-attribute name="value" value="localhost"/>
            </node>
            <node class="string" name="p2j_account">
              <node-attribute name="value" value="appserver_agent"/>
            </node>
            <node class="boolean" name="sessionFree">
              <node-attribute name="value" value="TRUE"/>
            </node>
            <node class="strings" name="aliases"/>
            <node class="strings" name="hosts">
              <node-attribute name="values" value="localhost"/>
            </node>
            <node class="integers" name="ports">
              <node-attribute name="values" value="21200"/>
              <node-attribute name="values" value="21300"/>
              <node-attribute name="values" value="21500"/>
            </node>
            <node class="strings" name="services">
              <node-attribute name="values" value="fwd1"/>
              <node-attribute name="values" value="21200"/>
              <node-attribute name="values" value="21300"/>
              <node-attribute name="values" value="21500"/>
            </node>
          </node>
        </node>

p2j_account needs to be specified only when connecting to a remote appserver; for only local 4GL SERVER:CONNECT method usage, this can be skipped.

Starting the Appserver

On FWD startup, all appserver configuration from the appservers node is read into AppServerDefinition. This will be used to determine the appserver details, when an Agent is started.

The appserver Agents are started automatically when the FWD server starts, assuming the Scheduler is configured for this. When the FWD client (process in this case) starts, StandardServer.standardEntry will check if AppServerManager.startAppServer() can handle an appserver Agent. A number of initial_agents appserver Agents (configured for an appserver) will be started automatically at FWD server startup.

If all agents started automatically at FWD server startup are busy, FWD will automatically launch an Agent for the target appserver, until max_agents is reached, when a appserver call is performed.

Appserver agents can also be manually started via ./server.sh -b <process> (assuming max_agents is not reached).

Agent trimming is done automatically, via the FWD's Scheduler support, which will schedule a AppServerLauncher.trimAgents task for each appserver, called every auto_trim_timeout seconds. When called, Agents will be terminated until min_agents is reached.

A new pool is created when the first agent is started for that pool, via AgentPool.newPool(appServer). This creates either a AgentPool$BoundPool or AgentPool$UnboundPool, depending on the appserver's type:
  • a BoundPool instance for State-aware and State-reset appservers
  • a UnboundPool instance for Stateless and State-free appservers
Each started Agent will have a Conversation, Reader and Writer thread, as for any other FWD client. The Agent's Conversation thread is not responsible for accepting directly an appserver request - the Conversation thread just waits for tasks to be posted. Instead, a remote request will be executed on the Dispatcher FWD server threads, which will call the proper AppServerManager API, to:
  • identify an available Agent and retrieve it from the appserver pool (it will wait for a number of request_timeout seconds until an Agent is available).
  • post the task to the Agent's Conversation thread.
  • wait for the task to complete.
  • return the result back to the caller.

For this reason, if there are remote appserver requests (this includes OpenClient calls when the client is remote and not running in the same JVM as the FWD server), it is important to configure at the FWD Server a number Dispatcher threads which can handle the maximum number of simultaneous remote connections.

When the request is originating from the same JVM thread as the FWD server, the Dispatcher threads will not be used, and the caller will be linked with the AppServerManager directly, via a local proxy.

Connecting to AppServer

Appserver connections can be established via the SERVER:CONNECT method or via an FWD Open Client connection. The appserver connection approach differs in each case, FWD providing support of the 4GL connection options only for the SERVER:CONNECT method.

Supported SERVER:CONNECT method options

FWD supports both PASOE and Classic appserver connection options:
  • for PASOE, the following options are supported:
    • -pf filename
    • -sessionModel [ Session-managed | Session-free ]
    • -URL Web-or-AppServer-path
  • for Classic Appserver, these options are supported:
    • -AppService application-service
    • -H [ host_name | IP-address ]
    • -S [ service-name | port-number ]
    • -DirectConnect - if set, then -H and -S specify the appserver, otherwise the nameserver target.
    • -URL Web-or-AppServer-path - when this is set, then host and port are extracted, -directConnect is assumed, and -S is assumed the URI path
    • -sessionModel [ Session-managed | Session-free ]

The following appserver connection options are not supported:

-ssl
-nosessionreuse
-nohostverify
-connectionLifetime
-initialConnections
-maxConnections
-nsClientMaxPort
-nsClientMinPort
-nsClientPicklistExpiration
-nsClientPicklistSize
-nsClientPortRetry
-nsClientDelay
-AppServerKeepalive

The target appserver is being resolved using the Configuring an Appserver Client options, following these rules:
  • if -sessionModel is set, then:
    • when Session-free, app_services/<appserver>/sessionFree must be set to true.
    • when Session-managed, app_services/<appserver>/sessionFree must be set to false.
  • either -directConnect is set or, otherwise, app_services/<appserver>/aliases must contain the -AppService name.
  • app_services/<appserver>/hosts contains the host from -H options or from the specified -URL; if neither is set, then it must contain localhost.
  • if -S port or -URL is set, then app_services/<appserver>/ports must have this port
  • if -S service is set, then app_services/<appserver>/services must have the service name
  • if neither -S or -URL are set, the port is assumed to be 5162, and app_services/<appserver>/ports must have this port
  • if neither -H or -URL is set, the host is assumed to be localhost, and app_services/<appserver>/hosts must have this host
  • if -AppService is not set and not in direct connnect mode, then -AppService is assumed to be the default service configured for that nameserver host (as the specified or implicit -H is assumed to target a nameserver).

Connecting from OpenClient

Connecting from a remote client is done by using the FWD OpenClient APIs. This requires first to configure the connection to the remote server, and after that call LegacyJavaAppserverClient.connectRemote to establish the appserver connection. Details are specified at Proxygen_REST_SOAP_and_WebHandler.

Runtime Details

A named appserver in FWD is a pool of agents which all share the same configuration (of which the most important are the propath and session mode) and which can be targeted via options specified in the connect parameters for a 4GL <server_handle>:CONNECT() method call, or the equivalent connect API for an Open Client. The agents in a pool (for an appserver) all share the same appservers/<appserver> configuration.

In FWD, the -sessionModel is not explicitly used to resolve the target appserver - all the other connection options (like -AppService or -URL) are used to find the appserver. Once this is found, when -sessionModel is specified, it means that:
  • if Session-Free, the target appserver must be State-free.
  • if Session-Managed, the target appserver must not be State-free.
Once an appserver connection is established, an Agent can be become bound in these conditions:
  • if the appserver is running in State-reset or State-aware mode, the Agent used to establish the connection becomes bound to the connection.
  • if the appserver is running in Stateless mode, an Agent is bound to that connection only when a program is ran persistent.
  • if the appserver is running in State-free mode, an Agent is not bound to the connection when a program is ran persistent, but any invocations targeting a persistent program will always route the request to the Agent which instantiated that persistent program.

Once the Agent is bound to the connection, it will not be released back to the agent pool, and is unavailable for work from other connections; only when it becomes unbound will be added back to the pool.

For an appserver, the work can be posted by the following callers:

Caller Type Description Proxy Proxy Setup Code Proxy Tear-Down Code RemoteObject Interface Used
local 4GL code call to appserver from converted code in the same FWD server local AppServerHelper.connectLegacyMode lifetime of FWD server AppServerEntry
remote 4GL code call to appserver from converted code in a different FWD server, this will occur over a virtual session RemoteObject AppServerHelper.connectLegacyMode AppServerHelper.disconnect AppServerEntry
local Java Open Client replacement call to appserver from hand coded Java in the same FWD server local
AppServerHelper.connect(BootstrapConfig  config,
                        String           account,
                        String           appServerName,
                        boolean          sessionFree)

Lifetime of FWD server. AppServerEntry
remote Java Open Client replacement call to appserver from hand coded Java in the a separate JVM, the call will come from a direct session through the dispatcher RemoteObject
AppServerHelper.connect(BootstrapConfig  config,
                        String           account,
                        String           appServerName,
                        boolean          sessionFree)

LegacyJavaAppserverClient.disconnect AppServerEntry
legacy REST internal usage of appserver agents as a worker for processing requests from legacy REST services local
AppServerHelper.connect(BootstrapConfig  config,
                        String           account,
                        String           appServerName,
                        boolean          sessionFree)

Lifetime of FWD server. AppServerEntry
legacy SOAP internal usage of appserver agents as a worker for processing requests from legacy SOAP services local
AppServerHelper.connect(BootstrapConfig  config,
                        String           account,
                        String           appServerName,
                        boolean          sessionFree)

Lifetime of FWD server. AppServerEntry
legacy WebHandler internal usage of appserver agents as a worker for processing requests from legacy WebHandler services local
AppServerHelper.connect(BootstrapConfig  config,
                        String           account,
                        String           appServerName,
                        boolean          sessionFree)

Lifetime of FWD server. AppServerEntry

For legacy REST, SOAP or web handler support, FWD uses an embedded web server (Jetty) to handle the HTTP requests. But, the request to the appserver is not being processed on the thread handling the web request - instead, a pool of workers (a distinct pool for each REST, SOAP or web handler support) is used. This worker pool will use a queue on which a task (as calculated on the web thread for that service request) will be posted.

The worker pool was added because the number of active web threads trying to perform i.e. REST requests can be more than the available Agents on the target appserver - if each one of these web threads would try to post a task to the appserver at the same time, the Agent pool would be exhausted immediately, and many request would fail with 'no server available' errors. Having this intermediate pool allows to queue the requests, with each Jetty thread being able to post the work to the worker thread with the fewest tasks on its queue - it will just have to wait for the worker thread to finish all the tasks posted before.

Local 4GL Code

Remote 4GL Code

Local Open Java Client Replacement

Remote Open Java Client Replacement

Legacy REST

Legacy SOAP

Legacy WebHandler

Debugging Details

All appserver invocations are exported via AppServerEntry proxy (at the client side), and implemented in AppServerManager, on FWD server-side.

AppServerManager will always acquire an Agent (the connection or procedure bound Agent, or one from the pool), and post the work to it. At the Agent FWD implementation, there are various APIs related to invoking an external program (persistent or not), an internal procedure, etc.

The interest when debugging the appserver call is to end up on the Agent's Conversation thread. The simplest way is to find a common API executed for all calls, and place a breakpoint in it - this is Agent.preProcessArguments, which converts the arguments to be compatible for being passed to the ControlFlowOps API call. Placing a breakpoint in this method allows you to exit just before the ControlFlowOps API call for the target. You can step through the code until this ControlFlowOps API is reached, and from that:
  • if you want to debug the ControlFlowOps, step further into through these APIs
  • otherwise, place a breakpoint in Block.body() method, and 'step over' the ControlFlowOps API - this will execute the code until the targeted converted code will be reached - you can continuing stepping into the bodyMethod.body(); call, and you will end up directly in the converted code.

Agent.preProcessArguments (which executes before ControlFlowOps) has a Agent.postProcessArguments paired method which always executes sometime after the ControlFlowOps API call finishes. Placing a breakpoint in postProcessArguments allows you to inspect the arguments as they are returned by the API call.


© 2004-2022 Golden Code Development Corporation. ALL RIGHTS RESERVED.

appserver_remote_open_java_client.png (149 KB) Greg Shah, 08/02/2022 07:12 AM

appserver_local_open_java_client.png (165 KB) Greg Shah, 08/02/2022 08:04 AM

appserver_local_4gl_code.png (165 KB) Greg Shah, 08/02/2022 08:56 AM

appserver_remote_4gl_code.png (177 KB) Greg Shah, 08/02/2022 08:56 AM

appserver_legacy_rest.png (187 KB) Greg Shah, 08/02/2022 10:04 AM

appserver_legacy_soap.png (189 KB) Greg Shah, 08/02/2022 10:04 AM

appserver_legacy_webhandler.png (203 KB) Greg Shah, 08/02/2022 10:04 AM