Skip to content

fix: setParameter 가 실패한 LobCreator 를 닫지도 등록하지도 않는 문제 수정 - #373

Open
wantaekchoi wants to merge 1 commit into
eGovFramework:mainfrom
wantaekchoi:fix/lobtypehandler-lobcreator-leak
Open

fix: setParameter 가 실패한 LobCreator 를 닫지도 등록하지도 않는 문제 수정#373
wantaekchoi wants to merge 1 commit into
eGovFramework:mainfrom
wantaekchoi:fix/lobtypehandler-lobcreator-leak

Conversation

@wantaekchoi

Copy link
Copy Markdown
Contributor

수정 사유 Reason for modification

  • 버그수정 Bug fixes
  • 기능개선 Enhancements
  • 기능추가 Adding features
  • 기타 Others

수정된 소스 내용 Modified source

AbstractLobTypeHandler.setParameter 가 만든 LobCreator 를 실패 경로에서 닫지 않습니다.

final LobCreator lobCreator = this.lobHandler.getLobCreator();          // :99

try {
    setParameterInternal(ps, i, parameter, jdbcType, lobCreator);       // :102
} catch (IOException ex) {
    throw new SQLException("I/O errors during LOB access: " + ex.getMessage());
}

TransactionSynchronizationManager.registerSynchronization(new LobCreatorSynchronization(lobCreator));  // :107

성공 경로는 :107 에서 동기화를 등록하고 그 동기화의 beforeCompletion 이 :192 에서 close 를 부릅니다. 이 클래스에서 close 를 부르는 자리는 :192 하나뿐입니다.

실패 경로는 :107 에 닿지 못합니다. setParameterInternalSQLException 을 던지면 그대로 전파되고 IOException 은 :104 에서 SQLException 으로 바뀌어 다시 던져집니다. 어느 쪽이든 그 LobCreator 는 닫히지도, 나중에 닫아줄 동기화에 등록되지도 않습니다.

클래스 Javadoc(:36-37)이 트랜잭션 동기화를 요구하는 이유를 이렇게 적습니다.

For writing LOBs, an active Spring transaction synchronization is required,
to be able to register a synchronization that closes the LobCreator.

만든 LobCreator 는 닫는다는 자기 선언입니다. 같은 패키지의 형제 BlobSerializableTypeHandlerObjectOutputStream(:116-117)과 ObjectInputStream(:144-145)을 finally 로 닫아 실패 경로까지 덮습니다.

이 동기화 클래스의 Javadoc(:173-174)은 "Invokes LobCreator.close to clean up temporary LOBs that might have been created." 입니다. 이 모듈이 쓰는 spring-jdbcDefaultLobHandlercreateTemporaryLob 을 켜면 TemporaryLobCreator 를 돌려주고, 그 close 가 만들어 둔 Blob·Clobfree 를 부릅니다. 닫히지 않으면 그만큼이 세션에 남습니다. 다만 실제 DB 세션에 남는 LOB 을 측정하지는 못했습니다.

AS-IS / TO-BE

형제와 같이 finally 로 정리하되, 동기화 등록 전에 빠져나가는 경우로 한정했습니다.

 final LobCreator lobCreator = this.lobHandler.getLobCreator();
+boolean succeeded = false;

 try {
     setParameterInternal(ps, i, parameter, jdbcType, lobCreator);
+    succeeded = true;
 } catch (IOException ex) {
     throw new SQLException("I/O errors during LOB access: " + ex.getMessage());
+} finally {
+    if (!succeeded) {
+        lobCreator.close();
+    }
 }

성공 경로에서도 닫지 않는 이유는, setParameter 가 돌아온 뒤 실제 실행 시점에 드라이버가 LobCreator 가 넘긴 스트림을 읽기 때문입니다. 그 시점을 아는 것은 트랜잭션 동기화이므로 성공 경로는 지금처럼 등록만 하고 넘어갑니다.

영향 범위

동작이 달라지는 것은 setParameterInternal 이 예외를 던지는 경우뿐이고 성공 경로의 동작은 그대로입니다. 이 setParameterfinal 이라 하위 클래스 3종(BlobByteArrayTypeHandler, ClobStringTypeHandler, BlobSerializableTypeHandler)이 모두 이 경로를 씁니다.

JUnit 테스트 JUnit tests

  • JUnit 테스트 JUnit tests
  • 수동 테스트 Manual testing

드라이버가 PreparedStatement.setBytes 에서 SQLException 을 내도록 해 BlobByteArrayTypeHandler.setParameter 를 실제로 태우고 진짜 DefaultLobCreator 를 감싼 프록시로 close 호출 횟수를 셉니다. 실패 뒤 등록된 동기화의 beforeCompletion 까지 모두 실행한 다음 세기 때문에, 직접 닫든 동기화에 등록하든 어느 처방이어도 통과합니다. 미수정 코드는 둘 다 하지 않아 0 회입니다.

수정 전입니다.

$ mvn -o test -pl Persistence/org.egovframe.rte.psl.dataaccess -Dtest=LobCreatorCleanupTest -DfailIfNoTests=true
[ERROR]   LobCreatorCleanupTest.testLobCreatorIsClosedWhenSetParameterFails:44 setParameter 가 실패해도 LobCreator 는 닫혀야 한다 ==> expected: <1> but was: <0>
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0
[INFO] BUILD FAILURE

수정 후 모듈 전체입니다.

$ mvn -o test -pl Persistence/org.egovframe.rte.psl.dataaccess
[INFO] Tests run: 127, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

테스트 브라우저 Test Browser

  • Chrome
  • Firefox
  • Edge
  • Safari
  • Opera
  • Internet Explorer
  • 기타 Others

테스트 스크린샷 또는 캡처 영상 Test screenshots or captured video

화면이 없는 실행환경 모듈이라 첨부하지 않았습니다.

setParameter 는 LobCreator 를 만든 뒤 setParameterInternal 이 성공해야
LobCreatorSynchronization 을 등록한다. 이 동기화가 beforeCompletion 에서
close 를 부르는 유일한 자리다. setParameterInternal 이 SQLException 이나
IOException 을 던지면 등록에 닿지 못하므로 그 LobCreator 는 닫히지 않는다.

클래스 Javadoc 은 LOB 을 쓰려면 트랜잭션 동기화가 활성이어야 하는 이유를
"LobCreator 를 닫는 동기화를 등록하기 위해서" 라고 적는다. 만든 LobCreator 는
반드시 닫힌다는 뜻이다. 같은 패키지의 BlobSerializableTypeHandler 도
ObjectOutputStream·ObjectInputStream 을 finally 에서 닫아 실패 경로를 함께 덮는다.

DefaultLobHandler 는 createTemporaryLob 을 켜면 TemporaryLobCreator 를 돌려주고
그 close 가 만들어 둔 Blob·Clob 의 free 를 부른다. 닫히지 않으면 그만큼이
세션에 남는다. 등록 전에 빠져나가는 경우에만 닫도록 finally 를 두었다.
성공 경로의 동작은 그대로다.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant