diff --git a/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/CobolFile.java b/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/CobolFile.java index 348c8697..1b6c65a4 100755 --- a/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/CobolFile.java +++ b/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/CobolFile.java @@ -907,6 +907,24 @@ private static String concatString(String... strs) { return sb.toString(); } + /** + * This method is mainly for unlocking the indexed files. + * + * @return true if post-processing is successful, false otherwise. + */ + protected boolean postProcess() { + return true; + } + + private void runPostProcess(AbstractCobolField fnstatus) { + postProcess(); + // TODO: Implement error handling + // boolean postProcessSucceeded = postProcess(); + // if(!postProcessSucceeded) { + // this.saveStatus(COB_STATUS_30_PERMANENT_ERROR, fnstatus); + // } + } + /** * TODO: 準備中 * @@ -1408,10 +1426,12 @@ public void read(AbstractCobolField key, AbstractCobolField fnstatus, int readOp if (this.flag_nonexistent) { if (this.flag_first_read == 0) { saveStatus(COB_STATUS_23_KEY_NOT_EXISTS, fnstatus); + runPostProcess(fnstatus); return; } this.flag_first_read = 0; saveStatus(COB_STATUS_10_END_OF_FILE, fnstatus); + runPostProcess(fnstatus); return; } @@ -1419,10 +1439,12 @@ public void read(AbstractCobolField key, AbstractCobolField fnstatus, int readOp if (key == null) { if (this.flag_end_of_file && (readOpts & COB_READ_PREVIOUS) == 0) { saveStatus(COB_STATUS_46_READ_ERROR, fnstatus); + runPostProcess(fnstatus); return; } if (this.flag_begin_of_file && (readOpts & COB_READ_PREVIOUS) != 0) { saveStatus(COB_STATUS_46_READ_ERROR, fnstatus); + runPostProcess(fnstatus); return; } } @@ -1431,6 +1453,7 @@ public void read(AbstractCobolField key, AbstractCobolField fnstatus, int readOp || this.open_mode == COB_OPEN_OUTPUT || this.open_mode == COB_OPEN_EXTEND) { saveStatus(COB_STATUS_47_INPUT_DENIED, fnstatus); + runPostProcess(fnstatus); return; } @@ -1558,6 +1581,7 @@ public void write(AbstractCobolField rec, int opt, AbstractCobolField fnstatus) || this.open_mode == COB_OPEN_INPUT || this.open_mode == COB_OPEN_I_O) { saveStatus(COB_STATUS_48_OUTPUT_DENIED, fnstatus); + runPostProcess(fnstatus); return; } } else { @@ -1565,6 +1589,7 @@ public void write(AbstractCobolField rec, int opt, AbstractCobolField fnstatus) || this.open_mode == COB_OPEN_INPUT || this.open_mode == COB_OPEN_EXTEND) { saveStatus(COB_STATUS_48_OUTPUT_DENIED, fnstatus); + runPostProcess(fnstatus); return; } } @@ -1578,6 +1603,7 @@ public void write(AbstractCobolField rec, int opt, AbstractCobolField fnstatus) if (this.record.getSize() < this.record_min || this.record_max < this.record.getSize()) { saveStatus(COB_STATUS_44_RECORD_OVERFLOW, fnstatus); + runPostProcess(fnstatus); return; } @@ -1657,20 +1683,24 @@ public void rewrite(AbstractCobolField rec, int opt, AbstractCobolField fnstatus if (this.open_mode == COB_OPEN_CLOSED || this.open_mode != COB_OPEN_I_O) { saveStatus(COB_STATUS_49_I_O_DENIED, fnstatus); + runPostProcess(fnstatus); return; } if (this.access_mode == COB_ACCESS_SEQUENTIAL && !readDone) { saveStatus(COB_STATUS_43_READ_NOT_DONE, fnstatus); + runPostProcess(fnstatus); return; } if (this.organization == COB_ORG_SEQUENTIAL) { if (this.record.getSize() != rec.getSize()) { saveStatus(COB_STATUS_44_RECORD_OVERFLOW, fnstatus); + runPostProcess(fnstatus); return; } if (this.record_size != null) { if (this.record.getSize() != this.record_size.getInt()) { saveStatus(COB_STATUS_44_RECORD_OVERFLOW, fnstatus); + runPostProcess(fnstatus); return; } } @@ -1721,11 +1751,13 @@ public void delete(AbstractCobolField fnstatus) { if (this.open_mode == COB_OPEN_CLOSED || this.open_mode != COB_OPEN_I_O) { saveStatus(COB_STATUS_49_I_O_DENIED, fnstatus); + runPostProcess(fnstatus); return; } if (this.access_mode == COB_ACCESS_SEQUENTIAL && !readDone) { saveStatus(COB_STATUS_43_READ_NOT_DONE, fnstatus); + runPostProcess(fnstatus); return; } diff --git a/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/CobolIndexedFile.java b/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/CobolIndexedFile.java index f82588c8..368dc37f 100644 --- a/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/CobolIndexedFile.java +++ b/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/CobolIndexedFile.java @@ -227,9 +227,9 @@ public int open_(String filename, int mode, int sharing) { return getConnectionStatus; } - if(fileExists) { + if (fileExists) { int code = this.checkVersionOld(); - if(code != COB_STATUS_00_SUCCESS) { + if (code != COB_STATUS_00_SUCCESS) { return code; } } @@ -303,11 +303,12 @@ private int checkVersionOld() { IndexedFile p = this.filei; try (Statement st = p.connection.createStatement()) { String fileLockTableExistsSql = - "select exists(select 1 from sqlite_master where type = 'table' and name = 'file_lock')"; + "select exists(select 1 from sqlite_master where type = 'table' and name =" + + " 'file_lock')"; ResultSet fileLockTableExistsResultSet = st.executeQuery(fileLockTableExistsSql); if (fileLockTableExistsResultSet.next()) { boolean fileLockTableExists = fileLockTableExistsResultSet.getInt(1) == 1; - if(!fileLockTableExists) { + if (!fileLockTableExists) { return COB_STATUS_92_VERSION_INCOMPATIBLE; } } else { @@ -700,15 +701,26 @@ private void unlockPreviousRecord() throws SQLException { String updateSql = String.format( "update %s set locked_by = null, process_id = null, locked_at = null where" - + " key = ?", + + " key = ? and locked_by = ?", getTableName(0)); try (PreparedStatement updateStatement = p.connection.prepareStatement(updateSql)) { updateStatement.setBytes(1, previousLockedRecordKey); + updateStatement.setString(2, this.getProcessUuid()); updateStatement.executeUpdate(); } previousLockedRecordKey = null; } + @Override + protected boolean postProcess() { + try { + unlockPreviousRecord(); + } catch (SQLException e) { + return false; + } + return true; + } + private void unlockPreviousRecord(byte[] key) throws SQLException { if (previousLockedRecordKey == null) { previousLockedRecordKey = key; @@ -718,10 +730,11 @@ private void unlockPreviousRecord(byte[] key) throws SQLException { String updateSql = String.format( "update %s set locked_by = null, process_id = null, locked_at = null where" - + " key = ?", + + " key = ? and locked_by = ?", getTableName(0)); try (PreparedStatement updateStatement = p.connection.prepareStatement(updateSql)) { updateStatement.setBytes(1, previousLockedRecordKey); + updateStatement.setString(2, this.getProcessUuid()); updateStatement.executeUpdate(); } previousLockedRecordKey = key; @@ -756,10 +769,14 @@ public int read_(AbstractCobolField key, int readOpts) { try { if (checkOtherProcessLockedRecord(primaryKey)) { p.connection.rollback(); + unlockPreviousRecord(); + p.connection.commit(); return COB_STATUS_51_RECORD_LOCKED; } if (!lockRecord(primaryKey)) { p.connection.rollback(); + unlockPreviousRecord(); + p.connection.commit(); return COB_STATUS_30_PERMANENT_ERROR; } unlockPreviousRecord(primaryKey); @@ -894,10 +911,14 @@ public int readNext(int readOpts) { try { if (checkOtherProcessLockedRecord(primaryKey)) { p.connection.rollback(); + unlockPreviousRecord(); + p.connection.commit(); return COB_STATUS_51_RECORD_LOCKED; } if (!lockRecord(primaryKey)) { p.connection.rollback(); + unlockPreviousRecord(); + p.connection.commit(); return COB_STATUS_30_PERMANENT_ERROR; } unlockPreviousRecord(primaryKey); @@ -1069,6 +1090,12 @@ public int write_(int opt) { } else if (this.access_mode == COB_ACCESS_SEQUENTIAL) { byte[] keyBytes = p.key; if (p.last_key.memcmp(keyBytes, keyBytes.length) > 0) { + try { + unlockPreviousRecord(); + p.connection.commit(); + } catch (SQLException e) { + return COB_STATUS_30_PERMANENT_ERROR; + } return COB_STATUS_21_KEY_INVALID; } } @@ -1145,6 +1172,12 @@ public int rewrite_(int opt) { if (this.access_mode == COB_ACCESS_SEQUENTIAL && !IndexedCursor.matchKeyHead(p.key, DBT_SET(this.keys[0].getField()))) { + try { + unlockPreviousRecord(); + p.connection.commit(); + } catch (SQLException e) { + return COB_STATUS_30_PERMANENT_ERROR; + } return COB_STATUS_21_KEY_INVALID; } diff --git a/tests/indexed-lock.src/release-lock.at b/tests/indexed-lock.src/release-lock.at index 743a4dc9..6e477e2e 100644 --- a/tests/indexed-lock.src/release-lock.at +++ b/tests/indexed-lock.src/release-lock.at @@ -315,3 +315,282 @@ AT_DATA([prog2.template.cbl], [ AT_CHECK([bash a.sh]) AT_CLEANUP + +AT_SETUP([unlock (READ)]) +AT_DATA([p1.cbl], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. p1. + + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT F ASSIGN TO "f.dat" + ORGANIZATION IS INDEXED + ACCESS MODE IS DYNAMIC + RECORD KEY IS REC-KEY + * LOCK MODE IS AUTOMATIC + ALTERNATE RECORD KEY IS REC-KEY2 WITH DUPLICATES + FILE STATUS IS FILE-STATUS. + + DATA DIVISION. + FILE SECTION. + FD f. + 01 REC. + 05 REC-KEY PIC X(5). + 05 REC-KEY2 PIC X(5). + 05 REC-DATA PIC X(5). + + WORKING-STORAGE SECTION. + 01 FILE-STATUS PIC XX. + 01 DUMMY PIC X(10) VALUE "DUMMY DATA". + PROCEDURE DIVISION. + MAIN-PROCEDURE. + OPEN OUTPUT f. + MOVE "AAAA1" TO REC-KEY. + MOVE "BBBB1" TO REC-KEY2. + MOVE "CCCC1" TO REC-DATA. + WRITE REC. + MOVE "AAAA2" TO REC-KEY. + MOVE "BBBB2" TO REC-KEY2. + MOVE "CCCC2" TO REC-DATA. + WRITE REC. + CLOSE f. + + OPEN I-O f. + + MOVE "AAAA1" TO REC-KEY. + READ F WITH LOCK. + DISPLAY FILE-STATUS. + + + CALL "setValue" USING + "playground-record-lock-01" + "AAAA1 record is locked". + CALL "wait" USING + "playground-record-lock-02" + "AAAA2 record is released". + + MOVE "AAAA2" TO REC-KEY. + REWRITE REC. + DISPLAY FILE-STATUS. + CLOSE f. + CALL "setValue" USING + "playground-record-lock-03" + "end of p1". +]) + +AT_DATA([p2.cbl], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. p2. + + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT F ASSIGN TO "f.dat" + ORGANIZATION IS INDEXED + ACCESS MODE IS DYNAMIC + RECORD KEY IS REC-KEY + ALTERNATE RECORD KEY IS REC-KEY2 WITH DUPLICATES + FILE STATUS IS FILE-STATUS. + + DATA DIVISION. + FILE SECTION. + FD f. + 01 REC. + 05 REC-KEY PIC X(5). + 05 REC-KEY2 PIC X(5). + 05 REC-DATA PIC X(5). + + WORKING-STORAGE SECTION. + 01 FILE-STATUS PIC XX. + 01 REC-EMPTY PIC X(15) VALUE "AAAA1 ". + 01 REC-BACKUP PIC X(15). + 01 DUMMY PIC X(15). + PROCEDURE DIVISION. + MAIN-PROCEDURE. + CALL "wait" USING + "playground-record-lock-01" + "AAAA1 record is locked". + + OPEN I-O f. + DISPLAY FILE-STATUS. + + INITIALIZE REC. + MOVE "AAAA2" TO REC. + READ F WITH LOCK. + + MOVE "AAAA1" TO REC-KEY. + READ F WITH LOCK. + DISPLAY FILE-STATUS. + + CALL "setValue" USING + "playground-record-lock-02" + "AAAA2 record is released". + + CALL "wait" USING + "playground-record-lock-03" + "end of p1". + + CLOSE f. +]) + +AT_DATA([run.sh], [ + java -Xlog:perf+memops=off p1 > p1.log & + PID1=$! + java -Xlog:perf+memops=off p2 > p2.log & + PID2=$! + wait $PID1 $PID2 +]) + +AT_CHECK([cp ../../indexed-lock.src/module-sync/wait.java .]) +AT_CHECK([cp ../../indexed-lock.src/module-sync/setValue.java .]) +AT_CHECK([cobj p1.cbl p2.cbl]) +AT_CHECK([javac wait.java setValue.java]) + +# run a test +AT_CHECK([bash run.sh] +) +AT_CHECK([cat p1.log], [0], +[00 +00 +]) + +AT_CHECK([cat p2.log], [0], +[00 +51 +]) + +AT_CLEANUP + +AT_SETUP([unlock (REWRITE)]) +AT_DATA([p1.cbl], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. p1. + + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT F ASSIGN TO "f.dat" + ORGANIZATION IS INDEXED + ACCESS MODE IS SEQUENTIAL + RECORD KEY IS REC-KEY + * LOCK MODE IS AUTOMATIC + ALTERNATE RECORD KEY IS REC-KEY2 WITH DUPLICATES + FILE STATUS IS FILE-STATUS. + + DATA DIVISION. + FILE SECTION. + FD f. + 01 REC. + 05 REC-KEY PIC X(5). + 05 REC-KEY2 PIC X(5). + 05 REC-DATA PIC X(5). + + WORKING-STORAGE SECTION. + 01 FILE-STATUS PIC XX. + 01 DUMMY PIC X(10) VALUE "DUMMY DATA". + PROCEDURE DIVISION. + MAIN-PROCEDURE. + OPEN OUTPUT f. + MOVE "AAAA1" TO REC-KEY. + MOVE "BBBB1" TO REC-KEY2. + MOVE "CCCC1" TO REC-DATA. + WRITE REC. + MOVE "AAAA2" TO REC-KEY. + MOVE "BBBB2" TO REC-KEY2. + MOVE "CCCC2" TO REC-DATA. + WRITE REC. + CLOSE f. + + OPEN I-O f. + MOVE "AAAA1" TO REC-KEY. + START f KEY IS = REC-KEY. + READ F WITH LOCK. + MOVE "AAAA2" TO REC-KEY. + REWRITE REC. + DISPLAY FILE-STATUS. + + + CALL "setValue" USING + "playground-record-lock-01" + "AAAA1 record is released". + CALL "wait" USING + "playground-record-lock-02" + "end of p2". + + CLOSE f. +]) + +AT_DATA([p2.cbl], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. p2. + + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT F ASSIGN TO "f.dat" + ORGANIZATION IS INDEXED + ACCESS MODE IS DYNAMIC + RECORD KEY IS REC-KEY + ALTERNATE RECORD KEY IS REC-KEY2 WITH DUPLICATES + FILE STATUS IS FILE-STATUS. + + DATA DIVISION. + FILE SECTION. + FD f. + 01 REC. + 05 REC-KEY PIC X(5). + 05 REC-KEY2 PIC X(5). + 05 REC-DATA PIC X(5). + + WORKING-STORAGE SECTION. + 01 FILE-STATUS PIC XX. + 01 REC-EMPTY PIC X(15) VALUE "AAAA1 ". + 01 REC-BACKUP PIC X(15). + 01 DUMMY PIC X(15). + PROCEDURE DIVISION. + MAIN-PROCEDURE. + CALL "wait" USING + "playground-record-lock-01" + "AAAA1 record is released". + + OPEN I-O f. + + INITIALIZE REC. + + MOVE "AAAA1" TO REC-KEY. + READ F WITH LOCK. + DISPLAY FILE-STATUS. + + CALL "setValue" USING + "playground-record-lock-02" + "end of p2". + + CLOSE f. +]) + +AT_DATA([run.sh], [ + java -Xlog:perf+memops=off p1 > p1.log & + PID1=$! + java -Xlog:perf+memops=off p2 > p2.log & + PID2=$! + wait $PID1 $PID2 +]) + +AT_CHECK([cp ../../indexed-lock.src/module-sync/wait.java .]) +AT_CHECK([cp ../../indexed-lock.src/module-sync/setValue.java .]) +AT_CHECK([cobj p1.cbl p2.cbl]) +AT_CHECK([javac wait.java setValue.java]) + +# run a test +AT_CHECK([bash run.sh]) + +AT_CHECK([cat p1.log], [0], +[21 +]) + +AT_CHECK([cat p2.log], [0], +[00 +]) + +AT_CLEANUP \ No newline at end of file