Skip to content

Commit 5584dba

Browse files
Make AbstractSession AutoCloseable (JAVA) and safer close
Enable safe automatic closing of ETP sessions and harden close semantics. - cmake/FetpapiClientUsingFesapi.java: Use try-with-resources for ClientSession so sessions are closed automatically and update messaging to reflect AutoCloseable behavior. - cmake/swigEtp1_2Include.i.in: Expose AbstractSession as java.lang.AutoCloseable via SWIG typemaps, add Java imports, implement a Java close() that calls closeAndBlock() then delete(), and add docs/nodefaultctor placement for AbstractSession. Fix #31 - src/fetpapi/etp/AbstractSession.h: Make the destructor call closeAndBlock() to ensure sockets are closed when instances are destroyed; guard close() and closeAndBlock() against repeated calls and log redundant attempts. These changes prevent resource leaks by making session lifetime management safer and allow Java callers to use try-with-resources for deterministic cleanup.
1 parent 6c9954e commit 5584dba

4 files changed

Lines changed: 108 additions & 85 deletions

File tree

cmake/FetpapiClientUsingFesapi.java

Lines changed: 74 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -101,84 +101,83 @@ public static void main(String[] args) {
101101
additionalHeaderField.put("data-partition-id", "osdu"); // Example for OSDU RDDMS
102102
initializationParams.setAdditionalHandshakeHeaderFields(additionalHeaderField);
103103

104-
ClientSession clientSession = fetpapi.createClientSession(initializationParams, authorization);
105-
clientSession.setCoreProtocolHandlers(new CoreHandlers(clientSession));
106-
clientSession.setDataspaceProtocolHandlers(new DataspaceHandlers(clientSession));
107-
clientSession.setDiscoveryProtocolHandlers(new DiscoveryHandlers(clientSession));
108-
clientSession.setStoreProtocolHandlers(new StoreHandlers(clientSession));
109-
clientSession.setDataArrayProtocolHandlers(new DataArrayHandlers(clientSession));
110-
new Thread(clientSession::run).start();
111-
long start = System.currentTimeMillis();
112-
while (clientSession.isEtpSessionClosed() && System.currentTimeMillis() - start < 5000) {
113-
TimeUnit.MILLISECONDS.sleep(1);
114-
}
115-
if (clientSession.isEtpSessionClosed()) {
116-
System.err.println("The ETP session cound not be establisehd in 5 seconds.");
117-
return;
118-
}
119-
System.out.println("Now connected to ETP Server");
120-
// ****** We are now connected to ETP server through clientSession ******
121-
// Set the HDF proxy factory in order to use one compliant with ETP
122-
repo.setHdfProxyFactory(new FesapiHdfProxyFactory(clientSession));
123-
124-
// ****** Get a dataspace content. This corresponds to getting the content of an EPC file ******
125-
// Find an available ETP dataspace
126-
DataspaceVector allDataspaces = clientSession.getDataspaces();
127-
Optional<Dataspace> dataspace = allDataspaces.stream().findAny();
128-
if (dataspace.isEmpty()) {
129-
clientSession.close();
130-
System.err.println("The ETP server has no dataspace.");
131-
return;
132-
}
133-
System.out.println("Working on dataspace " + dataspace.get().getUri());
134-
// List resources of this ETP dataspace
135-
ContextInfo etpContext = new ContextInfo();
136-
etpContext.setUri(dataspace.get().getUri());
137-
etpContext.setDepth(1);
138-
ResourceVector allResources = clientSession.getResources(etpContext, ContextScopeKind.self);
139-
if (allResources.isEmpty()) {
140-
clientSession.close();
141-
System.err.println("The ETP dataspace has no resource.");
142-
return;
143-
}
144-
// Get dataobjects from the resources to the DataObjectRepository
145-
MapStringString uriMap = new MapStringString();
146-
long index = 0;
147-
for (Resource resource : allResources) {
148-
uriMap.put(Long.toString(index++), resource.getUri());
149-
}
150-
MapStringDataObject allDataObjects = clientSession.getDataObjects(uriMap);
151-
for (DataObject dataObject : allDataObjects.values()) {
152-
repo.addOrReplaceGsoapProxy(dataObject.getData(), fetpapi.getDataObjectType(dataObject.getResource().getUri()), fetpapi.getDataspaceUri(dataObject.getResource().getUri()));
153-
}
154-
// ****** We have now in the DataObjectRepository the same content as if we would have deserialized and EPC file looking like the dataspace ******
155-
156-
// ****** Use the DataObjectRepository exactly as you are used to do with FESAPI ******
157-
if (repo.getIjkGridRepresentationCount() > 0) {
158-
IjkGridExplicitRepresentation ijkGrid = repo.getIjkGridExplicitRepresentation(0);
159-
ijkGrid.loadSplitInformation();
160-
long originIndex = ijkGrid.getXyzPointIndexFromCellCorner(0, 0, 0, 0);
161-
System.out.println("The index of the grid origin in XYZ points is : " + originIndex);
162-
ijkGrid.unloadSplitInformation();
163-
if (ijkGrid.getValuesPropertyCount() > 0) {
164-
AbstractValuesProperty prop = ijkGrid.getValuesProperty(0);
165-
SWIGTYPE_p_double propValues = fesapi.new_DoubleArray(prop.getValuesCountOfPatch(0));
166-
try {
167-
prop.getDoubleValuesOfPatch(0, propValues);
168-
System.out.println("The first cell value of prop " + prop.getTitle() + " is " + fesapi.DoubleArray_getitem(propValues, 0));
169-
}
170-
finally {
171-
fesapi.delete_DoubleArray(propValues);
104+
try (ClientSession clientSession = fetpapi.createClientSession(initializationParams, authorization)) {
105+
clientSession.setCoreProtocolHandlers(new CoreHandlers(clientSession));
106+
clientSession.setDataspaceProtocolHandlers(new DataspaceHandlers(clientSession));
107+
clientSession.setDiscoveryProtocolHandlers(new DiscoveryHandlers(clientSession));
108+
clientSession.setStoreProtocolHandlers(new StoreHandlers(clientSession));
109+
clientSession.setDataArrayProtocolHandlers(new DataArrayHandlers(clientSession));
110+
new Thread(clientSession::run).start();
111+
long start = System.currentTimeMillis();
112+
while (clientSession.isEtpSessionClosed() && System.currentTimeMillis() - start < 5000) {
113+
TimeUnit.MILLISECONDS.sleep(1);
114+
}
115+
if (clientSession.isEtpSessionClosed()) {
116+
System.err.println("The ETP session cound not be establisehd in 5 seconds.");
117+
return;
118+
}
119+
System.out.println("Now connected to ETP Server");
120+
// ****** We are now connected to ETP server through clientSession ******
121+
// Set the HDF proxy factory in order to use one compliant with ETP
122+
repo.setHdfProxyFactory(new FesapiHdfProxyFactory(clientSession));
123+
124+
// ****** Get a dataspace content. This corresponds to getting the content of an EPC file ******
125+
// Find an available ETP dataspace
126+
DataspaceVector allDataspaces = clientSession.getDataspaces();
127+
Optional<Dataspace> dataspace = allDataspaces.stream().findAny();
128+
if (dataspace.isEmpty()) {
129+
clientSession.close();
130+
System.err.println("The ETP server has no dataspace.");
131+
return;
132+
}
133+
System.out.println("Working on dataspace " + dataspace.get().getUri());
134+
// List resources of this ETP dataspace
135+
ContextInfo etpContext = new ContextInfo();
136+
etpContext.setUri(dataspace.get().getUri());
137+
etpContext.setDepth(1);
138+
ResourceVector allResources = clientSession.getResources(etpContext, ContextScopeKind.self);
139+
if (allResources.isEmpty()) {
140+
clientSession.close();
141+
System.err.println("The ETP dataspace has no resource.");
142+
return;
143+
}
144+
// Get dataobjects from the resources to the DataObjectRepository
145+
MapStringString uriMap = new MapStringString();
146+
long index = 0;
147+
for (Resource resource : allResources) {
148+
uriMap.put(Long.toString(index++), resource.getUri());
149+
}
150+
MapStringDataObject allDataObjects = clientSession.getDataObjects(uriMap);
151+
for (DataObject dataObject : allDataObjects.values()) {
152+
repo.addOrReplaceGsoapProxy(dataObject.getData(), fetpapi.getDataObjectType(dataObject.getResource().getUri()), fetpapi.getDataspaceUri(dataObject.getResource().getUri()));
153+
}
154+
// ****** We have now in the DataObjectRepository the same content as if we would have deserialized and EPC file looking like the dataspace ******
155+
156+
// ****** Use the DataObjectRepository exactly as you are used to do with FESAPI ******
157+
if (repo.getIjkGridExplicitRepresentationCount() > 0) {
158+
IjkGridExplicitRepresentation ijkGrid = repo.getIjkGridExplicitRepresentation(0);
159+
ijkGrid.loadSplitInformation();
160+
long originIndex = ijkGrid.getXyzPointIndexFromCellCorner(0, 0, 0, 0);
161+
System.out.println("The index of the grid origin in XYZ points is : " + originIndex);
162+
ijkGrid.unloadSplitInformation();
163+
if (ijkGrid.getValuesPropertyCount() > 0) {
164+
AbstractValuesProperty prop = ijkGrid.getValuesProperty(0);
165+
SWIGTYPE_p_double propValues = fesapi.new_DoubleArray(prop.getValuesCountOfPatch(0));
166+
try {
167+
prop.getDoubleValuesOfPatch(0, propValues);
168+
System.out.println("The first cell value of prop " + prop.getTitle() + " is " + fesapi.DoubleArray_getitem(propValues, 0));
169+
}
170+
finally {
171+
fesapi.delete_DoubleArray(propValues);
172+
}
172173
}
173174
}
175+
else {
176+
System.out.println("This dataspace has no IJK Grid");
177+
}
178+
179+
System.out.println("Closing the session thanks to AutoCloseable...");
174180
}
175-
else {
176-
System.out.println("This dataspace has no IJK Grid");
177-
}
178-
179-
// Do not forget to close session once you have processed all the dataobject repository.
180-
System.out.println("Closing the session...");
181-
clientSession.close();
182181
} catch (InterruptedException e) {
183182
e.printStackTrace();
184183
}

cmake/swigEtp1_2Include.i.in

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,6 +1404,11 @@ namespace Energistics {
14041404
#endif
14051405

14061406
#ifdef SWIGJAVA
1407+
%typemap(javaimports) ETP_NS::AbstractSession %{
1408+
import java.lang.AutoCloseable;
1409+
import com.f2i_consulting.fetpapi.*;
1410+
%}
1411+
%typemap(javainterfaces) ETP_NS::AbstractSession "AutoCloseable"
14071412
%typemap(javacode) ETP_NS::AbstractSession %{
14081413
private CoreHandlers coreHandlersReference;
14091414
private DiscoveryHandlers discoveryHandlersReference;
@@ -1414,6 +1419,12 @@ namespace Energistics {
14141419
private TransactionHandlers transactionHandlersReference;
14151420
private DataspaceHandlers dataspaceHandlersReference;
14161421
private DataspaceOSDUHandlers dataspaceOSDUHandlersReference;
1422+
1423+
@Override
1424+
public void close() {
1425+
closeAndBlock();
1426+
delete();
1427+
}
14171428
%}
14181429

14191430
%typemap(javain,
@@ -1640,8 +1651,12 @@ namespace ETP_NS
16401651
virtual void on_CopyToDataspace(const Energistics::Etp::v12::Protocol::DataspaceOSDU::CopyToDataspace& msg, int64_t correlationId);
16411652
virtual void on_CopyToDataspaceResponse(const Energistics::Etp::v12::Protocol::DataspaceOSDU::CopyToDataspaceResponse & msg, int64_t correlationId);
16421653
};
1643-
1654+
16441655
%nodefaultctor AbstractSession;
1656+
/**
1657+
* The most abstract class defining a websocket ETP session.
1658+
* When such an instance is deleted, this class ensures that the socket is closed.
1659+
*/
16451660
class AbstractSession
16461661
{
16471662
public:
@@ -1722,13 +1737,6 @@ namespace ETP_NS
17221737
/****************
17231738
***** CORE ******
17241739
****************/
1725-
1726-
/**
1727-
* Send a message for closing to the server after having sent all previous messages.
1728-
* The session would really close only after all messages have been sent and responded.
1729-
* This method does not block.
1730-
*/
1731-
void close();
17321740

17331741
/**
17341742
* Send a message for closing to the server after having sent all previous messages.

python/example/etp_client_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,5 +130,5 @@ def start_etp_server(client_session):
130130
print("This dataspace has no 2d Grid")
131131

132132
repo.clear()
133-
client_session.close()
133+
client_session.closeAndBlock()
134134
print("FINISHED")

src/fetpapi/etp/AbstractSession.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,17 @@ using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
5050

5151
namespace ETP_NS
5252
{
53+
/**
54+
* The most abstract class defining a websocket ETP session.
55+
* When such an instance is deleted, this class ensures that the socket is closed.
56+
*/
5357
class AbstractSession : public std::enable_shared_from_this<AbstractSession>
5458
{
5559
public:
5660

57-
virtual ~AbstractSession() = default;
61+
virtual ~AbstractSession() {
62+
closeAndBlock();
63+
}
5864

5965
/**
6066
* Return the identifier of the session which is an UUID.
@@ -369,6 +375,11 @@ namespace ETP_NS
369375
* This method does not block.
370376
*/
371377
FETPAPI_DLL_IMPORT_OR_EXPORT void close() {
378+
if (isCloseRequested_) {
379+
fesapi_log("A session close has already been requested.");
380+
return;
381+
}
382+
372383
isCloseRequested_ = true;
373384
const bool shouldSendCloseMsgNow = [this]() {
374385
std::scoped_lock lock(sendingQueueMutex, specificProtocolHandlersMutex);
@@ -391,6 +402,11 @@ namespace ETP_NS
391402
* By default, it is true.
392403
*/
393404
FETPAPI_DLL_IMPORT_OR_EXPORT void closeAndBlock(bool forceCloseAfterTimeOut = true) {
405+
if (webSocketSessionClosed) {
406+
fesapi_log("The websocket session is already closed.");
407+
return;
408+
}
409+
394410
close();
395411
auto t_start = std::chrono::steady_clock::now();
396412
while (!webSocketSessionClosed) {

0 commit comments

Comments
 (0)