Project

General

Profile

proposal-issue-major-functional-3.txt

Sergey Ivanovskiy, 08/12/2026 02:56 AM

Download (8.96 KB)

 
1
================================================================================================
2
PROPOSAL - [MAJOR] functional - WebPageHandler.WebPageKeysProvider
3
PING_PONG_INTERVAL is exported to the page unclamped, so the two ends disagree
4
================================================================================================
5

    
6
REVIEW FINDING (abridged)
7
   "the PING_PONG_INTERVAL template supplier emits the raw configured value unclamped, while both Java
8
   readers changed by this revision (GuiWebDriver.init and ChuiWebSimulator) now sanitise
9
   pingPongInterval <= 0 to the 30000 default, so the two sides of the same heartbeat derive from
10
   different numbers. [...] The server pings every 30 s; the page computes pingPongInterval =
11
   Math.max(0, 100) = 100 and maxLostPings = 2, so checkServerSilence/checkLostPings tear a healthy
12
   socket down roughly 300 ms after every connect, giving a permanent reconnect loop. [...] This is
13
   also a regression: pre-revision the same misconfiguration merely made the client ping every 100 ms
14
   and the socket stayed up. AUTH_COOKIE_TTL and CLIENT_RESPONSE_TIMEOUT in the same provider are
15
   clamped for exactly this reason - the clamp belongs in one place so all three readers agree."
16

    
17
FILES
18
   src/com/goldencode/p2j/ui/client/driver/web/WebPageHandler.java            (primary)
19
   src/com/goldencode/p2j/web/WebConfigurationConstants.java                 (shared helper)
20
   src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebDriver.java          (de-duplicate)
21
   src/com/goldencode/p2j/ui/client/chui/driver/web/ChuiWebSimulator.java     (de-duplicate)
22

    
23
------------------------------------------------------------------------------------------------
24
1. ROOT CAUSE
25
------------------------------------------------------------------------------------------------
26

    
27
Three readers of one key, two of them clamping and one not:
28

    
29
   WebPageHandler.java:251-252      raw   config.getInt(PING_PONG_INTERVAL, 30000)     -> the page
30
   GuiWebDriver.java:652-658        clamped <= 0 to 30000                              -> the server
31
   ChuiWebSimulator.java:166-172    clamped <= 0 to 30000                              -> the server
32

    
33
r16675 introduced the two clamps and left the export alone, so a non-positive configured value now
34
produces a server that pings every 30 s and a page that expects to hear something every 100 ms:
35

    
36
   p2j.socket.js   pingPongInterval = Math.max(0, 100) = 100
37
                   maxLostPings     = Math.max(trunc(maxIdleTime / (2 * 100)), 2)  -> 2 when
38
                                      neither socketTimeout nor maxIdleTime is configured
39
                                      (maxIdleTime exports as CONNECTION_TIMEOUT = -1)
40
                   checkServerSilence fires every 100 ms, checkLostPings tears the socket down once
41
                   lostPings > 2
42

    
43
i.e. teardown ~300 ms after every connect, then attemptToRestoreConnection() - a permanent reconnect
44
loop. Reachable two ways, both plausible: pingPongInterval=0 (the "disable the heartbeat" attempt the
45
new Java comment explicitly anticipates) and pingPongInterval=-1 (the "unset" convention used by the
46
neighbouring items socketTimeout, watchdogTimeout and broadcastChannelPingTimeout in the same
47
directory section). Nothing validates the value - dir_schema.xml has no client/web object class - so
48
it is accepted verbatim from directory.xml.
49

    
50
Pre-r16675 the same misconfiguration was benign: the page pinged every 100 ms and nothing tore the
51
socket down. This is therefore a regression, not a pre-existing wart.
52

    
53
------------------------------------------------------------------------------------------------
54
2. PROPOSED FIX
55
------------------------------------------------------------------------------------------------
56

    
57
Put the clamp in one place and have all three readers call it. Duplicating the same "if (x <= 0)"
58
block in a third site would leave the next reader free to forget it again.
59

    
60
2.1 One accessor in WebConfigurationConstants
61
----------------------------------------------
62

    
63
WebConfigurationConstants is an interface of constants; add the clamp as a static helper next to the
64
constant it defends (or, if a constants-only interface is preferred by convention, put the helper on
65
WebPageHandler and call it from the two drivers - the important property is one implementation):
66

    
67
   /** The default heartbeat interval, at which the server pings the client and expects an echo back */
68
   int PING_PONG_INTERVAL = 30000;
69

    
70
   /**
71
    * Sanitise a configured heartbeat interval.
72
    * <p>
73
    * A non-positive value reads as "disable the heartbeat", but there is no such mode: the interval
74
    * also derives the dead peer deadline on the server and the silence watchdog on the page. All
75
    * readers of {@code client/web/pingPongInterval} must clamp through here, so that the value the
76
    * page is given and the value the server pings at can never diverge.
77
    *
78
    * @param    interval
79
    *           The configured interval, in milliseconds.
80
    *
81
    * @return   The interval to use, always positive.
82
    */
83
   static int sanitizePingPongInterval(int interval)
84
   {
85
      return (interval > 0) ? interval : PING_PONG_INTERVAL;
86
   }
87

    
88
2.2 WebPageHandler: clamp the export
89
-------------------------------------
90

    
91
WebPageHandler.java:251-252:
92

    
93
   -  add(ConfigItem.PING_PONG_INTERVAL.name(), () -> String.valueOf(
94
   -        config.getInt(ConfigItem.PING_PONG_INTERVAL, WebConfigurationConstants.PING_PONG_INTERVAL)));
95
   +  // heartbeat interval in milliseconds; clamp non-positive values to the default so the page's
96
   +  // silence watchdog and the server's ping cadence are derived from the same number
97
   +  add(ConfigItem.PING_PONG_INTERVAL.name(), () -> String.valueOf(
98
   +        WebConfigurationConstants.sanitizePingPongInterval(
99
   +           config.getInt(ConfigItem.PING_PONG_INTERVAL,
100
   +                         WebConfigurationConstants.PING_PONG_INTERVAL))));
101

    
102
This matches the shape of the two neighbouring clamped suppliers (AUTH_COOKIE_TTL at :256-260,
103
CLIENT_RESPONSE_TIMEOUT at :263-268) added for exactly this reason.
104

    
105
2.3 The two drivers: call the shared helper
106
--------------------------------------------
107

    
108
GuiWebDriver.java:652-658:
109

    
110
   -  pingPongInterval = config.getInt(ConfigItem.PING_PONG_INTERVAL, PING_PONG_INTERVAL);
111
   -  // 0 reads as "disable the heartbeat" but there is no such mode: the interval also derives the
112
   -  // dead peer deadline, and MIN_PING_INTERVAL would floor it to a 1s ping and a 3s deadline
113
   -  if (pingPongInterval <= 0)
114
   -  {
115
   -     pingPongInterval = PING_PONG_INTERVAL;
116
   -  }
117
   +  pingPongInterval = WebConfigurationConstants.sanitizePingPongInterval(
118
   +        config.getInt(ConfigItem.PING_PONG_INTERVAL, PING_PONG_INTERVAL));
119

    
120
ChuiWebSimulator.java:166-172: the identical replacement. This also disposes of the style finding
121
about the missing blank line after the GuiWebDriver block, since the block itself is gone.
122

    
123
2.4 Optional hardening: an upper bound too
124
-------------------------------------------
125

    
126
An absurdly large value is also accepted today (WebClientBuilderParameters.java:107 already puts
127
20 * 60 * 1000 = 20 min). Nothing breaks - the server clamps the interval down to idleMs / 3 - so this
128
is optional, but if a bound is wanted, clamp to the idle tolerance in one place rather than in each
129
reader.
130

    
131
------------------------------------------------------------------------------------------------
132
3. WHY NOT "MAKE THE PAGE TOLERANT INSTEAD"
133
------------------------------------------------------------------------------------------------
134

    
135
An alternative is to floor pingPongInterval inside p2j.socket.js (it already does
136
Math.max(0, cfg...)). That is strictly worse: it leaves three independent derivations of one key and
137
the divergence would simply reappear at a different value. The page should receive a value it can
138
trust; the clamp belongs at the single point where the configuration is read.
139

    
140
------------------------------------------------------------------------------------------------
141
4. RISK AND VERIFICATION
142
------------------------------------------------------------------------------------------------
143

    
144
Risk: negligible. The only behaviour change is for values that are currently broken. Sites that set 0
145
or -1 today are in a reconnect loop and will start working.
146

    
147
Verification
148
   1. Set client/web/pingPongInterval to 0 in a test directory.xml, launch the Web GUI: the page
149
      config must show 30000, the socket must stay up, and the server log must report
150
      "Keep-alive ping started with interval=30000".
151
   2. Repeat with -1 (the neighbouring items' "unset" convention).
152
   3. Repeat with a valid value (10000) and confirm nothing changed.
153
   4. Same three cases in web CHUI (ChuiWebSimulator path).
154
   5. Grep for any further readers of ConfigItem.PING_PONG_INTERVAL before landing, so the "one
155
      implementation" property holds: currently WebPageHandler, GuiWebDriver, ChuiWebSimulator,
156
      WebClientBuilderOptions.java:309 and WebClientSpawner.java:644 (the last two only forward the
157
      raw option to the spawned client, which then reads it through the clamped paths).