The AdminConsole client reported import success before the sensor-fix import
had even started. The client's completion signal is a non-null
DataImportProgress result, but ImportMasterDataOperation.internalApplyTo set
the result and overall progress 1.0 at the end of the object-graph apply() --
which returns to MasterDataImporter.importFromStream, where the wind-track and
sensor-fix imports still run afterward. So "Done"/1.0/result fired prematurely.
Move ownership of terminal completion to MasterDataImporter.importFromStream,
the true end of the whole import (object graph + wind + fixes):
- ImportMasterDataOperation.internalApplyTo no longer sets the result, overall
1.0, or logs "Done"; it ends at overall 0.6 and returns creationCount.
- MasterDataImporter publishes the single terminal completion after the wind
band (0.6->0.8) and sensor-fix band (0.8->1.0):
createOrUpdateDataImportProgressWithReplication(id, 1.0, IMPORT_SENSOR_FIXES,
1.0, creationCount). The result is set only at true 1.0, with replication,
so replica-polling clients observe completion correctly.
- Plumb a nullable MasterDataImportObjectCreationCount through a new 5-arg
createOrUpdateDataImportProgressWithReplication overload and the
CreateOrUpdateDataImportProgress operation, which sets the result on replicas
via progress.setResult(result) when non-null.
- createOrUpdateDataImportProgressWithoutReplication now uses getDataImportLock()
instead of the raw field so subclass overrides (the test mock) are honored;
a no-op in production where the getter returns the field.
serialVersionUIDs left unchanged: adding a nullable field is serialization-
compatible and master/replicas upgrade together.
Verified: MasterDataImportTest 13/13 green; both projects compile clean.
Assisted-By: Claude Opus (claude-opus-5)
Follow-up to fd6739d9a6 (stream GPS fixes as top-level objects), addressing
four defects on the master data import path:
- Merge DeviceMappingDescriptor objects per DeviceIdentifier into a single
MultiTimeRange so a device mapped in several regattas (or via several
open-ended mapping events) has its fixes streamed once over coalesced,
non-overlapping sub-ranges rather than once per mapping. Obsoletes and
removes the nested DeviceMappingDescriptor class; the field becomes
Map<DeviceIdentifier, MultiTimeRange> raceLogTrackingDeviceRanges. The
inclusive mapping end is carried through and streamed with
toIsInclusive=true, at worst streaming a harmless superset (store dedups).
- Apply the top-level-streaming + per-object reset() design to
WindTrackMasterData: strip the wind tracks out of the serialized graph,
stream one WindTrackMasterData at a time on export (held only transiently),
and read+store them incrementally on import (master only). Bounds retained
memory to a single wind track at a time instead of the whole season.
- Remove the premature "Done importing master data into ..." log and the
overall 1.0 from ImportMasterDataOperation.internalApplyTo; that fired
before the sensor-fix import ran. The single terminal "Done" and 1.0 now
live in MasterDataImporter.importFromStream, the true end of the import.
- Make progress rise monotonically and reserve the two phases that usually
dominate the import: the object graph now ends at 0.6, the wind-track
import occupies 0.6 -> 0.8 (20%), and the sensor-fix import occupies
0.8 -> 1.0 (20%), instead of reaching 1.0 in the operation and then
dropping back in the importer.
Assisted-By: Claude Opus 4.8 (claude-opus-5)
Master data import/export (MDI) previously materialized every mapped
device's GPS fixes in memory before serialization: the fixes lived inside
the single TopLevelMasterData object graph written with one writeObject, so
ObjectOutputStream/ObjectInputStream had to retain a handle for every fix to
resolve potential back-references. For events with many fixes this dominated
heap on both the exporting and importing server.
Change the wire format so fixes are streamed as top-level stream objects
after the (now fix-free) TopLevelMasterData:
- TopLevelMasterData no longer carries the fixes themselves. It records
lightweight, fully Serializable DeviceFixMappingDescriptor entries
(device, from, toInclusive) plus a transient SensorFixStore used only on
the export side. Default serialization now does the right thing; the
stripped replica copy carries no descriptors.
- MasterDataResource.writeRaceLogTrackingFixes streams each device section
straight from the SensorFixStore: device header, each fix as a top-level
object, a null fix terminator, then ObjectOutputStream.reset() once per
device so no fix handle is retained across devices while the fix class
descriptor stays shared within a device. A trailing null device ends the
section. Framing uses null sentinels, never fix counts (a concurrent write
could invalidate a count).
- MasterDataImporter.importRaceLogTrackingGPSFixes reads that section
incrementally and stores fixes in batches of BATCH_SIZE_FOR_IMPORTING_FIXES
directly into the master's SensorFixStore, holding at most one batch at a
time. This stream-based import only ever runs on the primary/master:
such requests must always be routed there, and a replica receives only the
stripped ImportMasterDataOperation (no fixes), obtaining tracking data by
the master loading tracked races whose transactions are replicated. A
non-null master descriptor here is therefore a broken routing state and is
reported by throwing IllegalStateException rather than silently draining.
IMPORT_SENSOR_FIXES sub-progress is advanced once per device section,
driven by the descriptor count, restoring the per-device granularity that
existed before.
- ImportMasterDataOperation no longer imports fixes; that logic moved to
MasterDataImporter, which has the target store in scope.
Exporter and importer are upgraded together; mixed-version transfer is not
required.
Verified: MasterDataImportTest green (full class 13/13 repeatedly;
testMasterDataImportWithoutHttpStack repeatedly), covering the 4999/5000/5001
batch-boundary fix counts and the DoubleVectorFix round trip.
Assisted-By: Claude Opus 4.8 (claude-opus-5)
Refactor RaceLiveContent to carry TimePoint values for tracking start and
end instead of long millis, matching the earlier LiveContentCheckResult
change and removing the "millis" naming where the value is now a TimePoint.
- RaceLiveContent: trackingStart/trackingEnd fields, constructor and getters
now use TimePoint; serialVersionUID regenerated for the new field layout.
- GWT RPC custom field serializer writes/reads the TimePoints via
writeObject/readObject.
- JSON serializer keeps the millis-long wire format (null-guarded) under the
unchanged trackingStartMillis/trackingEndMillis keys; deserializer now uses
asLong(...) for both values and wraps non-null millis in TimePoint.of(...),
making the deserialization null-safe for both start and end.
- LiveContentResource passes TrackedRace.getStartOfTracking()/getEndOfTracking()
through directly rather than calling asMillis() on a possibly-null value.
- RacingEventService.getAllTrackedRacesForEventTrackingAt Javadoc completed and
extended to document that every returned TrackedRace has a non-null start of
tracking (end of tracking may still be null).
- Updated LandscapeManagementPanel display and TestLiveContentValueTypes.
Assisted-By: Claude Opus 4.8 (Claude Code)
this avoids most of the blocking calls to
TrackedRegatta.getTrackedRace(RaceDefinition) which can lead to endless
waits, depleting pool and other threads when called while a race is
being removed