_lock2.diff
src/com/goldencode/p2j/jmx/FwdJMX.java 2022-03-28 09:44:42 +0000 | ||
---|---|---|
2 | 2 |
** Module : FwdJMX.java |
3 | 3 |
** Abstract : FWD JMX instrumentation. |
4 | 4 |
** |
5 |
** Copyright (c) 2020-2021, Golden Code Development Corporation.
|
|
5 |
** Copyright (c) 2020-2022, Golden Code Development Corporation.
|
|
6 | 6 |
** |
7 | 7 |
** -#- -I- --Date-- ---------------------------------Description---------------------------------- |
8 | 8 |
** 001 IAS 20200930 Created initial version. |
... | ... | |
10 | 10 |
** HC 20201010 Implemented map counter. |
11 | 11 |
** 003 SBI 20211104 Added ThreadsCpuTimers to monitor CPU utilization by threads. |
12 | 12 |
** CA 20211216 Added a task counter for the legacy service worker threads. |
13 |
** IAS 20220327 Added 'registerMBean' method. |
|
13 | 14 |
*/ |
14 | 15 | |
15 | 16 |
/* |
... | ... | |
153 | 154 |
} |
154 | 155 |
|
155 | 156 |
/** |
157 |
* Register MBean |
|
158 |
* |
|
159 |
* @param mbean |
|
160 |
* MBean to be registered |
|
161 |
* @param name |
|
162 |
* MBean name |
|
163 |
*/ |
|
164 |
public void registerMBean(Object mbean, String name) |
|
165 |
{ |
|
166 |
try |
|
167 |
{ |
|
168 |
MBeanServer server = ManagementFactory.getPlatformMBeanServer(); |
|
169 |
server.registerMBean(mbean, new ObjectName("com.goldencode.p2j:name=" + name)); |
|
170 |
} |
|
171 |
catch (Exception e) |
|
172 |
{ |
|
173 |
LOG.log(Level.SEVERE, "MBean registratiion faild", e); |
|
174 |
} |
|
175 |
} |
|
176 | ||
177 |
/** |
|
156 | 178 |
* Initialize singleton |
157 | 179 |
*/ |
158 | 180 |
public static void init() |
... | ... | |
161 | 183 |
} |
162 | 184 |
|
163 | 185 |
/** |
186 |
* Register MBean |
|
187 |
* |
|
188 |
* @param mbean |
|
189 |
* MBean to be registered |
|
190 |
* @param name |
|
191 |
* MBean name |
|
192 |
*/ |
|
193 |
public static void register(Object mbean, String name) |
|
194 |
{ |
|
195 |
Holder.INSTANCE.registerMBean(mbean, name); |
|
196 |
} |
|
197 | ||
198 |
/** |
|
164 | 199 |
* Instance holder (Lazy initialization) |
165 | 200 |
*/ |
166 | 201 |
private static class Holder |
... | ... | |
209 | 244 |
/** |
210 | 245 |
* Return the string representation of the counter |
211 | 246 |
* |
212 |
* @param ts |
|
213 |
* @param comment |
|
214 |
* comment |
|
215 |
* @return string representation of the counter |
|
216 |
* @throws IOException |
|
247 |
* @param ts |
|
248 |
* The measurement time stamp |
|
249 |
* @param comment |
|
250 |
* The comment |
|
251 |
* |
|
252 |
* @return The string representation of the counter |
|
253 |
* |
|
254 |
* @throws IOException |
|
255 |
* If IO exception occurs |
|
217 | 256 |
*/ |
218 | 257 |
@Override |
219 | 258 |
public String toString(String ts, String comment) throws IOException |
... | ... | |
430 | 469 |
* Return the string representation of the counter |
431 | 470 |
* |
432 | 471 |
* @param ts |
472 |
* The measurement time stamp |
|
433 | 473 |
* @param comment |
434 | 474 |
* comment |
435 | 475 |
* @return string representation of the counter |
436 | 476 |
* @throws IOException |
477 |
* If IO exception occurs |
|
437 | 478 |
*/ |
438 | 479 |
public String toString(String ts, String comment) throws IOException |
439 | 480 |
{ |
... | ... | |
492 | 533 | |
493 | 534 |
/** |
494 | 535 |
* Return the string representation of the counter |
495 |
* |
|
496 |
* @param ts |
|
497 |
* @param comment |
|
498 |
* comment |
|
499 |
* @return string representation of the counter |
|
500 |
* @throws IOException |
|
536 |
* |
|
537 |
* @param ts |
|
538 |
* The measurement time stamp |
|
539 |
* @param comment |
|
540 |
* The comment |
|
541 |
* |
|
542 |
* @return The string representation of the counter |
|
543 |
* |
|
544 |
* @throws IOException |
|
545 |
* If IO exception occurs |
|
501 | 546 |
*/ |
502 | 547 |
public String toString(String ts, String comment) throws IOException |
503 | 548 |
{ |
... | ... | |
519 | 564 |
* @param file |
520 | 565 |
* output file name (will be appended if exists) |
521 | 566 |
* @throws IOException |
567 |
* If IO exception occurs |
|
522 | 568 |
*/ |
523 | 569 |
@Override |
524 | 570 |
public synchronized void dumpAll(String file) throws IOException |
src/com/goldencode/p2j/jmx/LockTableUpdaterStat.java 2022-03-27 15:58:12 +0000 | ||
---|---|---|
1 |
/* |
|
2 |
** Module : LockTableUpdaterStat.java |
|
3 |
** Abstract : LockTableUpdater JMX instrumentation. |
|
4 |
** |
|
5 |
** Copyright (c) 2022, Golden Code Development Corporation. |
|
6 |
** |
|
7 |
** -#- -I- --Date-- ---------------------------------Description---------------------------------- |
|
8 |
** 001 IAS 20230327 Created initial version. |
|
9 |
*/ |
|
10 | ||
11 |
/* |
|
12 |
** This program is free software: you can redistribute it and/or modify |
|
13 |
** it under the terms of the GNU Affero General Public License as |
|
14 |
** published by the Free Software Foundation, either version 3 of the |
|
15 |
** License, or (at your option) any later version. |
|
16 |
** |
|
17 |
** This program is distributed in the hope that it will be useful, |
|
18 |
** but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
19 |
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
20 |
** GNU Affero General Public License for more details. |
|
21 |
** |
|
22 |
** You may find a copy of the GNU Affero GPL version 3 at the following |
|
23 |
** location: https://www.gnu.org/licenses/agpl-3.0.en.html |
|
24 |
** |
|
25 |
** Additional terms under GNU Affero GPL version 3 section 7: |
|
26 |
** |
|
27 |
** Under Section 7 of the GNU Affero GPL version 3, the following additional |
|
28 |
** terms apply to the works covered under the License. These additional terms |
|
29 |
** are non-permissive additional terms allowed under Section 7 of the GNU |
|
30 |
** Affero GPL version 3 and may not be removed by you. |
|
31 |
** |
|
32 |
** 0. Attribution Requirement. |
|
33 |
** |
|
34 |
** You must preserve all legal notices or author attributions in the covered |
|
35 |
** work or Appropriate Legal Notices displayed by works containing the covered |
|
36 |
** work. You may not remove from the covered work any author or developer |
|
37 |
** credit already included within the covered work. |
|
38 |
** |
|
39 |
** 1. No License To Use Trademarks. |
|
40 |
** |
|
41 |
** This license does not grant any license or rights to use the trademarks |
|
42 |
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks |
|
43 |
** of Golden Code Development Corporation. You are not authorized to use the |
|
44 |
** name Golden Code, FWD, or the names of any author or contributor, for |
|
45 |
** publicity purposes without written authorization. |
|
46 |
** |
|
47 |
** 2. No Misrepresentation of Affiliation. |
|
48 |
** |
|
49 |
** You may not represent yourself as Golden Code Development Corporation or FWD. |
|
50 |
** |
|
51 |
** You may not represent yourself for publicity purposes as associated with |
|
52 |
** Golden Code Development Corporation, FWD, or any author or contributor to |
|
53 |
** the covered work, without written authorization. |
|
54 |
** |
|
55 |
** 3. No Misrepresentation of Source or Origin. |
|
56 |
** |
|
57 |
** You may not represent the covered work as solely your work. All modified |
|
58 |
** versions of the covered work must be marked in a reasonable way to make it |
|
59 |
** clear that the modified work is not originating from Golden Code Development |
|
60 |
** Corporation or FWD. All modified versions must contain the notices of |
|
61 |
** attribution required in this license. |
|
62 |
*/ |
|
63 | ||
64 |
package com.goldencode.p2j.jmx; |
|
65 | ||
66 |
/** LockTableUpdater JMX instrumentation */ |
|
67 |
public class LockTableUpdaterStat |
|
68 |
implements LockTableUpdaterStatMBean |
|
69 |
{ |
|
70 |
/** LockTableUpdater worker */ |
|
71 |
private final LockTableUpdaterStatMBean worker; |
|
72 |
|
|
73 |
|
|
74 |
/** |
|
75 |
* Constructor. |
|
76 |
* @param worker |
|
77 |
* LockTableUpdater worker |
|
78 |
*/ |
|
79 |
public LockTableUpdaterStat(LockTableUpdaterStatMBean worker) |
|
80 |
{ |
|
81 |
super(); |
|
82 |
this.worker = worker; |
|
83 |
} |
|
84 | ||
85 |
/** |
|
86 |
* Get number of processed lock events. |
|
87 |
* @return number of processed lock events. |
|
88 |
*/ |
|
89 |
@Override |
|
90 |
public long getLockEvents() |
|
91 |
{ |
|
92 |
return worker.getLockEvents(); |
|
93 |
} |
|
94 | ||
95 |
/** |
|
96 |
* Get number of active locks |
|
97 |
* @return number of active locks. |
|
98 |
*/ |
|
99 |
@Override |
|
100 |
public long getActiveLocks() |
|
101 |
{ |
|
102 |
return worker.getActiveLocks(); |
|
103 |
} |
|
104 | ||
105 |
/** |
|
106 |
* Get number of locks in VST. |
|
107 |
* @return number of locks in VST. |
|
108 |
*/ |
|
109 |
@Override |
|
110 |
public long getPersistedLocks() |
|
111 |
{ |
|
112 |
return worker.getPersistedLocks(); |
|
113 |
} |
|
114 | ||
115 |
/** |
|
116 |
* Get number of VST records deleted not in batch. |
|
117 |
* @return number of VST records deleted not in batch. |
|
118 |
*/ |
|
119 |
@Override |
|
120 |
public long getDeletesFromVST() |
|
121 |
{ |
|
122 |
return worker.getDeletesFromVST(); |
|
123 |
} |
|
124 | ||
125 |
/** |
|
126 |
* Get number of 'persist' calls. |
|
127 |
* @return number of 'persist' calls. |
|
128 |
*/ |
|
129 |
@Override |
|
130 |
public long getPersistCalls() |
|
131 |
{ |
|
132 |
return worker.getPersistCalls(); |
|
133 |
} |
|
134 | ||
135 |
/** |
|
136 |
* Force 'persist' call |
|
137 |
*/ |
|
138 |
@Override |
|
139 |
public void flush() |
|
140 |
{ |
|
141 |
worker.flush(); |
|
142 |
} |
|
143 |
|
|
144 |
} |
|
145 |
src/com/goldencode/p2j/jmx/LockTableUpdaterStatMBean.java 2022-03-27 15:58:13 +0000 | ||
---|---|---|
1 |
/* |
|
2 |
** Module : LockTableUpdaterStatMBean.java |
|
3 |
** Abstract : LockTableUpdater JMX instrumentation interface. |
|
4 |
** |
|
5 |
** Copyright (c) 2022, Golden Code Development Corporation. |
|
6 |
** |
|
7 |
** -#- -I- --Date-- ---------------------------------Description---------------------------------- |
|
8 |
** 001 IAS 20230327 Created initial version. |
|
9 |
*/ |
|
10 | ||
11 |
/* |
|
12 |
** This program is free software: you can redistribute it and/or modify |
|
13 |
** it under the terms of the GNU Affero General Public License as |
|
14 |
** published by the Free Software Foundation, either version 3 of the |
|
15 |
** License, or (at your option) any later version. |
|
16 |
** |
|
17 |
** This program is distributed in the hope that it will be useful, |
|
18 |
** but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
19 |
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
20 |
** GNU Affero General Public License for more details. |
|
21 |
** |
|
22 |
** You may find a copy of the GNU Affero GPL version 3 at the following |
|
23 |
** location: https://www.gnu.org/licenses/agpl-3.0.en.html |
|
24 |
** |
|
25 |
** Additional terms under GNU Affero GPL version 3 section 7: |
|
26 |
** |
|
27 |
** Under Section 7 of the GNU Affero GPL version 3, the following additional |
|
28 |
** terms apply to the works covered under the License. These additional terms |
|
29 |
** are non-permissive additional terms allowed under Section 7 of the GNU |
|
30 |
** Affero GPL version 3 and may not be removed by you. |
|
31 |
** |
|
32 |
** 0. Attribution Requirement. |
|
33 |
** |
|
34 |
** You must preserve all legal notices or author attributions in the covered |
|
35 |
** work or Appropriate Legal Notices displayed by works containing the covered |
|
36 |
** work. You may not remove from the covered work any author or developer |
|
37 |
** credit already included within the covered work. |
|
38 |
** |
|
39 |
** 1. No License To Use Trademarks. |
|
40 |
** |
|
41 |
** This license does not grant any license or rights to use the trademarks |
|
42 |
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks |
|
43 |
** of Golden Code Development Corporation. You are not authorized to use the |
|
44 |
** name Golden Code, FWD, or the names of any author or contributor, for |
|
45 |
** publicity purposes without written authorization. |
|
46 |
** |
|
47 |
** 2. No Misrepresentation of Affiliation. |
|
48 |
** |
|
49 |
** You may not represent yourself as Golden Code Development Corporation or FWD. |
|
50 |
** |
|
51 |
** You may not represent yourself for publicity purposes as associated with |
|
52 |
** Golden Code Development Corporation, FWD, or any author or contributor to |
|
53 |
** the covered work, without written authorization. |
|
54 |
** |
|
55 |
** 3. No Misrepresentation of Source or Origin. |
|
56 |
** |
|
57 |
** You may not represent the covered work as solely your work. All modified |
|
58 |
** versions of the covered work must be marked in a reasonable way to make it |
|
59 |
** clear that the modified work is not originating from Golden Code Development |
|
60 |
** Corporation or FWD. All modified versions must contain the notices of |
|
61 |
** attribution required in this license. |
|
62 |
*/ |
|
63 | ||
64 |
package com.goldencode.p2j.jmx; |
|
65 | ||
66 |
/** LockTableUpdater JMX instrumentation interface. */ |
|
67 |
public interface LockTableUpdaterStatMBean |
|
68 |
{ |
|
69 |
/** |
|
70 |
* Get number of processed lock events. |
|
71 |
* @return number of processed lock events. |
|
72 |
*/ |
|
73 |
public long getLockEvents(); |
|
74 |
|
|
75 |
/** |
|
76 |
* Get number of active locks |
|
77 |
* @return number of active locks. |
|
78 |
*/ |
|
79 |
public long getActiveLocks(); |
|
80 |
|
|
81 |
/** |
|
82 |
* Get number of locks in VST. |
|
83 |
* @return number of locks in VST. |
|
84 |
*/ |
|
85 |
public long getPersistedLocks(); |
|
86 | ||
87 |
/** |
|
88 |
* Get number of VST records deleted not in batch. |
|
89 |
* @return number of VST records deleted not in batch. |
|
90 |
*/ |
|
91 |
public long getDeletesFromVST(); |
|
92 |
|
|
93 |
/** |
|
94 |
* Get number of 'persist' calls. |
|
95 |
* @return number of 'persist' calls. |
|
96 |
*/ |
|
97 |
public long getPersistCalls(); |
|
98 | ||
99 |
/** |
|
100 |
* Force 'persist' call |
|
101 |
*/ |
|
102 |
public void flush(); |
|
103 |
} |
src/com/goldencode/p2j/persist/DatabaseManager.java 2022-04-08 21:02:27 +0000 | ||
---|---|---|
2 | 2 |
** Module : DatabaseManager.java |
3 | 3 |
** Abstract : Registers and configures databases and manages session factories |
4 | 4 |
** |
5 |
** Copyright (c) 2004-2021, Golden Code Development Corporation.
|
|
5 |
** Copyright (c) 2004-2022, Golden Code Development Corporation.
|
|
6 | 6 |
** |
7 | 7 |
** -#- -I- --Date-- --JPRM-- -----------------------------------Description----------------------------------- |
8 | 8 |
** 001 ECF 20050901 @22473 Created initial version. Extracted and |
... | ... | |
261 | 261 |
** implemented - unknown is not an acceptable returned value for this function. |
262 | 262 |
** Replaced the UnimplementedFeature.missing from tenant-related APIs with a TODO |
263 | 263 |
** comment. |
264 |
** IAS 20220324 Re-worked LockTableUpdater. |
|
264 | 265 |
*/ |
265 | 266 | |
266 | 267 |
/* |
... | ... | |
1238 | 1239 |
|
1239 | 1240 |
if (lockTableUpdaters != null) |
1240 | 1241 |
{ |
1241 |
LockTableUpdater ltu = new LockTableUpdater(metaDB); |
|
1242 |
LockTableUpdater ltu = new LockTableUpdater(primaryDB, metaDB);
|
|
1242 | 1243 |
lockTableUpdaters.put(primaryDB, ltu); |
1243 | 1244 |
} |
1244 | 1245 |
} |
... | ... | |
1534 | 1535 |
* if lock metadata is used by the current application, but no lock table updater is |
1535 | 1536 |
* found for the given database. |
1536 | 1537 |
*/ |
1537 |
static LockTableUpdater getLockTableUpdater(Database database) |
|
1538 |
public static LockTableUpdater getLockTableUpdater(Database database)
|
|
1538 | 1539 |
{ |
1539 | 1540 |
if (lockTableUpdaters == null) |
1540 | 1541 |
{ |
src/com/goldencode/p2j/persist/Persistence.java 2022-03-24 18:35:30 +0000 | ||
---|---|---|
564 | 564 |
** ECF 20210927 Minor cleanup. |
565 | 565 |
** IAS 20211223 Special processing of errors raised by UDFs. |
566 | 566 |
** IAS 20220221 Call a correct version of the getFWDVersion (Java or SQL) |
567 |
** IAS 20220324 Re-worked LockTableUpdater. |
|
567 | 568 |
*/ |
568 | 569 | |
569 | 570 |
/* |
... | ... | |
1114 | 1115 |
} |
1115 | 1116 |
|
1116 | 1117 |
/** |
1117 |
* Force the lock table updater to process all record lock events which are pending at the time |
|
1118 |
* this method is invoked. Additional events may be added by other threads while this is |
|
1119 |
* happening, but any events pending at the start of this method will be processed before this |
|
1120 |
* method returns. |
|
1121 |
*/ |
|
1122 |
public void flushMetaLockEvents() |
|
1123 |
{ |
|
1124 |
if (lockTableUpdater != null) |
|
1125 |
{ |
|
1126 |
lockTableUpdater.flushQueue(); |
|
1127 |
} |
|
1128 |
} |
|
1129 |
|
|
1130 |
/** |
|
1131 | 1118 |
* Get the identity manager associated with this <code>Persistence</code> |
1132 | 1119 |
* instance. For temp tables, this will be <code>null</code>, since for |
1133 | 1120 |
* temp tables unique primary keys generated using {@link |
src/com/goldencode/p2j/persist/RecordBuffer.java 2022-04-05 20:29:52 +0000 | ||
---|---|---|
1198 | 1198 |
** OM 20220228 Added byOuterQuery flag for marking situations when the buffer is empty because |
1199 | 1199 |
** it is set by an OUTER-JOIN table. |
1200 | 1200 |
** OM 20220301 Merged [unknownMode] and [byOuterQuery] flags. |
1201 |
** IAS 20220324 Re-worked LockTableUpdater. |
|
1201 | 1202 |
** OM 20220328 Count and return errors in [endBatch] method. |
1202 | 1203 |
*/ |
1203 | 1204 | |
... | ... | |
3206 | 3207 |
public static int endBatch(boolean error) |
3207 | 3208 |
{ |
3208 | 3209 |
BufferManager bufMan = BufferManager.get(); |
3210 |
|
|
3209 | 3211 |
Set<RecordBuffer> bufs = bufMan.endBatchAssignMode(); |
3210 | 3212 |
int fails = 0; |
3211 | 3213 |
|
... | ... | |
3319 | 3321 |
{ |
3320 | 3322 |
fails++; |
3321 | 3323 |
} |
3322 |
|
|
3323 | 3324 |
if (commitPending) |
3324 | 3325 |
{ |
3325 | 3326 |
buffer.setCommitPending(false); |
... | ... | |
3349 | 3350 |
{ |
3350 | 3351 |
cleanupBatchMode(bufs); |
3351 | 3352 |
} |
3352 |
|
|
3353 | 3353 |
return fails; |
3354 | 3354 |
} |
3355 | 3355 |
|
... | ... | |
6705 | 6705 |
ErrorManager.recordOrShowError(14378, msg, true); |
6706 | 6706 |
} |
6707 | 6707 |
|
6708 |
if (legacyName.equalsIgnoreCase("_Lock")) |
|
6709 |
{ |
|
6710 |
persistence.flushMetaLockEvents(); |
|
6711 |
} |
|
6712 |
else if (legacyName.equalsIgnoreCase("_Trans")) |
|
6708 |
if (legacyName.equalsIgnoreCase("_Trans")) |
|
6713 | 6709 |
{ |
6714 | 6710 |
// flush the transaction id to persistence if required |
6715 | 6711 |
TransactionManager.flushTransMetaData(getPersistence()); |
src/com/goldencode/p2j/persist/meta/LockTableUpdater.java 2022-03-28 09:38:35 +0000 | ||
---|---|---|
2 | 2 |
** Module : LockTableUpdater.java |
3 | 3 |
** Abstract : Manages metadata lock records for a particular, primary database |
4 | 4 |
** |
5 |
** Copyright (c) 2013-2020, Golden Code Development Corporation.
|
|
5 |
** Copyright (c) 2013-2022, Golden Code Development Corporation.
|
|
6 | 6 |
** |
7 | 7 |
** -#- -I- --Date-- ---------------------------------------Description--------------------------------------- |
8 | 8 |
** 001 ECF 20131101 Created initial version. |
... | ... | |
23 | 23 |
** 014 ECF 20200906 New ORM implementation. |
24 | 24 |
** 015 CA 20200924 Replaced Method.invoke with ReflectASM. |
25 | 25 |
** OM 20201012 Force use locally cached meta information instead of map lookup. |
26 |
** IAS 20220324 Performance optimization in the UserTableStatUpdater style. |
|
26 | 27 |
*/ |
27 | 28 | |
28 | 29 |
/* |
... | ... | |
82 | 83 | |
83 | 84 |
import java.lang.reflect.*; |
84 | 85 |
import java.util.*; |
86 |
import java.util.concurrent.*; |
|
87 |
import java.util.concurrent.atomic.*; |
|
88 |
import java.util.concurrent.locks.*; |
|
85 | 89 |
import java.util.logging.*; |
86 | 90 | |
91 |
import javax.management.loading.*; |
|
92 | ||
93 |
import org.apache.jasper.tagplugins.jstl.core.*; |
|
94 | ||
95 |
import com.goldencode.p2j.jmx.*; |
|
87 | 96 |
import com.goldencode.p2j.persist.*; |
88 | 97 |
import com.goldencode.p2j.persist.lock.*; |
89 | 98 |
import com.goldencode.p2j.persist.orm.*; |
... | ... | |
168 | 177 |
|
169 | 178 |
/** |
170 | 179 |
* Constructor. |
171 |
* |
|
180 |
* @param primary |
|
181 |
* Primary database |
|
172 | 182 |
* @param metaDatabase |
173 | 183 |
* Metadata database. |
174 | 184 |
*/ |
175 |
public LockTableUpdater(Database metaDatabase) |
|
176 |
{ |
|
177 |
this.worker = new UpdateWorker(PersistenceFactory.getInstance(metaDatabase)); |
|
178 |
} |
|
179 |
|
|
180 |
/** |
|
181 |
* This method is called within a user context to force any pending lock events to be |
|
182 |
* processed. It guarantees that any events that are pending in the queue at the time this |
|
183 |
* method is invoked will have been processed by the time this method returns. However, |
|
184 |
* other events may be added by other user contexts in the meantime, and these are not |
|
185 |
* guaranteed to have been processed. |
|
186 |
*/ |
|
187 |
public void flushQueue() |
|
188 |
{ |
|
189 |
worker.forceFlush(); |
|
190 |
} |
|
191 |
|
|
192 |
/** |
|
193 |
* Stop processing record lock events for the associated, primary database. This will terminate |
|
194 |
* the update worker and its processing thread. |
|
195 |
*/ |
|
196 |
public void terminate() |
|
197 |
{ |
|
198 |
worker.terminate(); |
|
199 |
} |
|
200 |
|
|
185 |
public LockTableUpdater(Database primary, Database metaDatabase) |
|
186 |
{ |
|
187 |
this.worker = new UpdateWorker( |
|
188 |
PersistenceFactory.getInstance(metaDatabase), |
|
189 |
primary); |
|
190 |
} |
|
191 |
|
|
192 |
/** |
|
193 |
* Persist locks in the meta database |
|
194 |
*/ |
|
195 |
public void persist() |
|
196 |
{ |
|
197 |
worker.persist(); |
|
198 |
} |
|
201 | 199 |
/** |
202 | 200 |
* Method which is called by a {@link LockManager} in response to a lock event. Does some |
203 | 201 |
* preliminary processing and sanity checking, then queues the event for further processing on |
... | ... | |
244 | 242 |
* thread processes the events. |
245 | 243 |
*/ |
246 | 244 |
private static class UpdateWorker |
247 |
implements Runnable
|
|
245 |
implements LockTableUpdaterStatMBean
|
|
248 | 246 |
{ |
249 | 247 |
/** Metadata lock table name */ |
250 | 248 |
private static final String LOCK_TABLE = "meta_lock"; |
251 | 249 |
|
252 |
/** SQL query to find a lock record by lock recid and user */
|
|
250 |
/** SQL query to find a lock record by PK */
|
|
253 | 251 |
private static final String SQL_LOCK = |
254 |
"select m." + Session.PK + " from " + LOCK_TABLE + " m " + |
|
255 |
"where m.lock_recid = ?1 and m.lock_usr = ?2"; |
|
256 |
|
|
257 |
/** SQL query to find a file number by converted table name (used by lock manager) */ |
|
258 |
private static final String SQL_FILENUM = |
|
259 |
"select m.file_number from meta_file m where m.user_misc = ?1"; |
|
252 |
"select m." + Session.PK + " from " + LOCK_TABLE + " m " + |
|
253 |
"where m." + Session.PK + " = ?1"; |
|
260 | 254 |
|
261 | 255 |
/** The offset between a table and its surrogate chain id. */ |
262 | 256 |
private static final int CHAIN_OFFSET = 1_000_000; |
... | ... | |
304 | 298 |
dmoClass = implementingClass; |
305 | 299 |
} |
306 | 300 |
|
307 |
/** Event queue to which many threads can write and from which only one thread reads */ |
|
308 |
private final List<RecordLockEvent> eventQueue = new LinkedList<>(); |
|
309 |
|
|
301 |
/** Number of processed lock events. */ |
|
302 |
private static final AtomicLong lockEvents = new AtomicLong(0); |
|
303 | ||
304 |
/** Number of records in VST. */ |
|
305 |
private static final AtomicLong persistedLocks = new AtomicLong(0); |
|
306 |
|
|
307 |
/** Number of VST records deleted not in batch. */ |
|
308 |
private static final AtomicLong deletesFromVST = new AtomicLong(0); |
|
309 |
|
|
310 |
/** Number of 'persist' calls */ |
|
311 |
private static final AtomicLong persistCalls = new AtomicLong(0); |
|
312 |
|
|
313 |
/** Executor for operations with VST */ |
|
314 |
private final Executor executor = Executors.newSingleThreadExecutor( |
|
315 |
new ThreadFactory() |
|
316 |
{ |
|
317 |
|
|
318 |
@Override |
|
319 |
public Thread newThread(Runnable r) |
|
320 |
{ |
|
321 |
Thread workThread = new AssociatedThread(r); |
|
322 |
workThread.setDaemon(true); |
|
323 |
workThread.setName("Lock metadata update thread"); |
|
324 |
return workThread; |
|
325 |
} |
|
326 |
} |
|
327 |
); |
|
328 |
/** Locks' map */ |
|
329 |
private final ConcurrentMap<LockRecId, MinimalLockImpl> locks = new ConcurrentSkipListMap<>(); |
|
330 | ||
331 |
/** Map version */ |
|
332 |
private final AtomicLong mapVersion = new AtomicLong(); |
|
333 | ||
334 |
/** Meta table vesrion */ |
|
335 |
private final AtomicLong databaseVersion = new AtomicLong(0); |
|
336 | ||
337 |
/** Locks' map guard */ |
|
338 |
private final ReentrantReadWriteLock guard = new ReentrantReadWriteLock(); |
|
339 |
|
|
340 |
/** database name */ |
|
341 |
private final Database primary; |
|
342 | ||
310 | 343 |
/** Persistence helper object for metadata database */ |
311 | 344 |
private final Persistence persistence; |
312 | 345 |
|
... | ... | |
316 | 349 |
/** Invocation handler which maps proxy method calls to underlying DMO methods */ |
317 | 350 |
private final Handler handler = new Handler(); |
318 | 351 |
|
319 |
/** Update worker thread */ |
|
320 |
private final AssociatedThread workThread; |
|
321 |
|
|
322 |
/** Cache of converted table names to file numbers */ |
|
323 |
private final Map<String, Integer> fileNums = new HashMap<>(); |
|
324 |
|
|
325 |
/** Termination flag */ |
|
326 |
private boolean done = false; |
|
327 |
|
|
328 |
/** Lock needed to read from queue; must be obtained first if write lock is needed also */ |
|
329 |
private final Object readLock = new Object(); |
|
330 |
|
|
331 |
/** Lock needed to write to queue; must be obtained second if read lock is needed also */ |
|
332 |
private final Object writeLock = new Object(); |
|
333 |
|
|
334 |
/** Unique and sequential ID of the last event added to the queue */ |
|
335 |
private long lastAddedID = -1L; |
|
336 |
|
|
337 |
/** Unique and sequential ID of the last event read from the queue and processed */ |
|
338 |
private long lastProcessedID = -1L; |
|
339 |
|
|
340 | 352 |
/** Next available primary key for a new lock metadata record */ |
341 |
private long nextPrimaryKey = 1L;
|
|
353 |
private AtomicLong nextPrimaryKey = new AtomicLong(1L);
|
|
342 | 354 |
|
343 | 355 |
/** Dummy empty class to be used as the super class for proxy. */ |
344 | 356 |
public static class Empty {} |
... | ... | |
349 | 361 |
* @param persistence |
350 | 362 |
* Persistence helper object for metadata database. |
351 | 363 |
*/ |
352 |
UpdateWorker(Persistence persistence) |
|
364 |
UpdateWorker(Persistence persistence, Database primary)
|
|
353 | 365 |
{ |
354 | 366 |
super(); |
355 | 367 |
|
356 | 368 |
this.persistence = persistence; |
369 |
this.primary = primary; |
|
357 | 370 |
this.proxy = ProxyFactory.getProxy(Empty.class, new Class<?>[] { MinimalLock.class }, handler); |
371 |
FwdJMX.register(new LockTableUpdaterStat(this), primary.getName() + ".locks"); |
|
372 |
executor.execute(() -> {}); // start executor |
|
373 |
} |
|
374 |
|
|
375 |
/** |
|
376 |
* Submit persisting locks task to the meta database and wait for end |
|
377 |
*/ |
|
378 |
public void persist() |
|
379 |
{ |
|
380 |
CountDownLatch latch = new CountDownLatch(1); |
|
381 |
executor.execute(() -> { |
|
382 |
try |
|
383 |
{ |
|
384 |
doPersist(); |
|
385 |
latch.countDown(); |
|
386 |
} |
|
387 |
catch(Throwable t) |
|
388 |
{ |
|
389 |
log.log(Level.WARNING, "Failed to persist lock table data", t); |
|
390 |
} |
|
391 |
}); |
|
392 |
try |
|
393 |
{ |
|
394 |
latch.await(); |
|
395 |
} |
|
396 |
catch (InterruptedException ignore) |
|
397 |
{ |
|
398 |
} |
|
399 |
} |
|
400 | ||
401 |
/** |
|
402 |
* Persist locks to the meta database |
|
403 |
*/ |
|
404 |
public void doPersist() |
|
405 |
{ |
|
406 |
persistCalls.incrementAndGet(); |
|
407 |
guard.writeLock().lock(); |
|
408 |
try |
|
409 |
{ |
|
410 |
if (databaseVersion.get() >= mapVersion.get()) |
|
411 |
{ |
|
412 |
return; |
|
413 |
} |
|
414 |
for(Iterator<Map.Entry<LockRecId, MinimalLockImpl>> it = locks.entrySet().iterator(); |
|
415 |
it.hasNext(); ) |
|
416 |
{ |
|
417 |
Map.Entry<LockRecId, MinimalLockImpl> e = it.next(); |
|
418 |
e.getValue().persist(); |
|
419 |
if (e.getValue().deleted) |
|
420 |
{ |
|
421 |
it.remove(); |
|
422 |
} |
|
423 |
} |
|
424 |
databaseVersion.set(mapVersion.get()); |
|
425 |
} |
|
426 |
finally |
|
427 |
{ |
|
428 |
guard.writeLock().unlock(); |
|
429 |
} |
|
358 | 430 |
|
359 |
workThread = new AssociatedThread(this); |
|
360 |
workThread.setDaemon(true); |
|
361 |
workThread.setName("Lock metadata update thread"); |
|
362 |
workThread.start(); |
|
363 |
} |
|
364 |
|
|
365 |
/** |
|
366 |
* This method executes in the worker thread, reading record lock events from the queue when |
|
367 |
* they become available, and updating the metadata database accordingly. |
|
368 |
*/ |
|
369 |
@Override |
|
370 |
public void run() |
|
371 |
{ |
|
372 |
while (true) |
|
373 |
{ |
|
374 |
// check if we should terminate event processing |
|
375 |
synchronized (writeLock) |
|
376 |
{ |
|
377 |
if (done) |
|
378 |
{ |
|
379 |
break; |
|
380 |
} |
|
381 |
} |
|
382 |
|
|
383 |
// process any pending events |
|
384 |
processPendingEvents(); |
|
385 |
|
|
386 |
// wait for new events to be added to the queue if it is still empty |
|
387 |
synchronized (writeLock) |
|
388 |
{ |
|
389 |
while (!done && eventQueue.isEmpty()) |
|
390 |
{ |
|
391 |
try |
|
392 |
{ |
|
393 |
writeLock.wait(); |
|
394 |
} |
|
395 |
catch (InterruptedException exc) |
|
396 |
{ |
|
397 |
// may be spurious, ignore; if interrupted due to termination, we will |
|
398 |
// detect this in the termination check above |
|
399 |
} |
|
400 |
} |
|
401 |
} |
|
402 |
} |
|
403 |
} |
|
404 |
|
|
431 |
} |
|
405 | 432 |
/** |
406 | 433 |
* Add a record lock event to the queue, locking out other write and read operations until |
407 | 434 |
* the queue is updated. Notify a the worker thread when the new event has been added. |
... | ... | |
411 | 438 |
*/ |
412 | 439 |
void addEvent(RecordLockEvent event) |
413 | 440 |
{ |
414 |
synchronized (writeLock) |
|
415 |
{ |
|
416 |
eventQueue.add(event); |
|
417 |
// DBG: System.out.println("-> " + event); |
|
418 |
lastAddedID = event.getEventID(); |
|
419 |
writeLock.notify(); |
|
420 |
} |
|
421 |
} |
|
422 |
|
|
423 |
/** |
|
424 |
* This method is called within a user context to force any pending lock events to be |
|
425 |
* processed. It guarantees that any events that are pending in the queue at the time this |
|
426 |
* method is invoked will have been processed by the time this method returns. However, |
|
427 |
* other events may be added by other user contexts in the meantime, and these are not |
|
428 |
* guaranteed to have been processed. |
|
429 |
*/ |
|
430 |
void forceFlush() |
|
431 |
{ |
|
432 |
long markerID = -1L; |
|
433 |
long lastProcessed = -1L; |
|
434 |
|
|
435 |
synchronized (writeLock) |
|
436 |
{ |
|
437 |
markerID = lastAddedID; |
|
438 |
lastProcessed = lastProcessedID; |
|
439 |
} |
|
440 |
|
|
441 |
synchronized (readLock) |
|
442 |
{ |
|
443 |
while (lastProcessed < markerID) |
|
444 |
{ |
|
445 |
try |
|
446 |
{ |
|
447 |
writeLock.notify(); |
|
448 |
readLock.wait(); |
|
449 |
} |
|
450 |
catch (InterruptedException exc) |
|
451 |
{ |
|
452 |
if (done) |
|
453 |
{ |
|
454 |
break; |
|
455 |
} |
|
456 |
} |
|
457 |
|
|
458 |
synchronized (writeLock) |
|
459 |
{ |
|
460 |
lastProcessed = lastProcessedID; |
|
461 |
} |
|
462 |
} |
|
463 |
} |
|
464 |
} |
|
465 |
|
|
466 |
/** |
|
467 |
* Set a flag to terminate the queue, removing any pending events which have not yet been |
|
468 |
* processed. The worker thread is interrupted. |
|
469 |
* <p> |
|
470 |
* This method should invoked when the primary database associated with the enclosing lock |
|
471 |
* table updater is deregistered, as part of cleaning up the associated metadata database. |
|
472 |
*/ |
|
473 |
void terminate() |
|
474 |
{ |
|
475 |
synchronized (writeLock) |
|
476 |
{ |
|
477 |
done = true; |
|
478 |
eventQueue.clear(); |
|
479 |
workThread.interrupt(); |
|
480 |
} |
|
481 |
} |
|
482 |
|
|
483 |
/** |
|
484 |
* Process all pending record lock events in the queue. Events are read and removed from the |
|
485 |
* queue synchronously, such that no new events may be added during the read/remove |
|
486 |
* operation. However, they are then processed asynchronously, so new events may be added |
|
487 |
* while lock metadata is being updated. |
|
488 |
*/ |
|
489 |
private void processPendingEvents() |
|
490 |
{ |
|
491 |
RecordLockEvent[] pending = null; |
|
492 |
|
|
493 |
synchronized (readLock) |
|
494 |
{ |
|
495 |
// collect all pending events (if any) |
|
496 |
synchronized (writeLock) |
|
497 |
{ |
|
498 |
int size = eventQueue.size(); |
|
499 |
if (size > 0) |
|
500 |
{ |
|
501 |
pending = new RecordLockEvent[size]; |
|
502 |
Iterator<RecordLockEvent> iter = eventQueue.iterator(); |
|
503 |
for (int i = 0; iter.hasNext(); i++) |
|
504 |
{ |
|
505 |
pending[i] = iter.next(); |
|
506 |
iter.remove(); |
|
507 |
} |
|
508 |
} |
|
509 |
} |
|
510 |
|
|
511 |
// process pending events (if any); note that new events can be added to the queue |
|
512 |
// while we are doing this |
|
513 |
if (pending != null) |
|
514 |
{ |
|
515 |
int len = pending.length; |
|
516 |
for (int i = 0; i < len; i++) |
|
517 |
{ |
|
518 |
processEvent(pending[i]); |
|
519 |
} |
|
520 |
|
|
521 |
synchronized (writeLock) |
|
522 |
{ |
|
523 |
lastProcessedID = pending[len - 1].getEventID(); |
|
524 |
} |
|
525 |
} |
|
526 |
|
|
527 |
// give any threads waiting in forceFlush a chance to proceed |
|
528 |
readLock.notifyAll(); |
|
529 |
} |
|
530 |
} |
|
531 |
|
|
532 |
/** |
|
533 |
* Process an individual record lock event, updating the metadata lock table as appropriate. |
|
534 |
* If the lock event represents a newly acquired lock, create a new lock record, otherwise |
|
535 |
* find the existing lock record associated with the event, which must represent a change of |
|
536 |
* the state of an existing lock. If that change of state represents a lock release, delete |
|
537 |
* the associated lock record from the lock table. |
|
538 |
* |
|
539 |
* @param event |
|
540 |
* Record lock event describing a new lock or a change in state of an existing |
|
541 |
* lock. |
|
542 |
*/ |
|
543 |
private void processEvent(RecordLockEvent event) |
|
544 |
{ |
|
545 |
Record dmo = null; |
|
546 |
boolean commit = false; |
|
547 |
|
|
441 |
lockEvents.incrementAndGet(); |
|
442 |
guard.readLock().lock(); |
|
548 | 443 |
try |
549 | 444 |
{ |
550 |
// DBG: System.out.println("<- " + event); |
|
551 |
commit = persistence.beginTransaction(); |
|
552 |
|
|
553 |
LockType oldType = event.getOldType(); |
|
554 |
LockType newType = event.getNewType(); |
|
555 |
|
|
556 |
if (LockType.NONE.equals(oldType)) |
|
557 |
{ |
|
558 |
dmo = createRecord(event); |
|
559 |
} |
|
560 |
else |
|
561 |
{ |
|
562 |
dmo = findRecord(event); |
|
563 |
} |
|
564 |
|
|
565 |
if (LockType.NONE.equals(newType)) |
|
566 |
{ |
|
567 |
persistence.delete(dmo); |
|
568 |
} |
|
569 |
else |
|
570 |
{ |
|
571 |
String code = newType.isExclusive() ? "X" : "S"; // TODO: get this right |
|
572 |
if (oldType.isShare()) |
|
573 |
{ |
|
574 |
code += " U"; // an upgrade |
|
575 |
} |
|
576 |
// From ABL online manual: values of flags field specify: |
|
577 |
// S: a share lock, |
|
578 |
// X: an exclusive lock, |
|
579 |
// U: a lock upgraded from share to exclusive, |
|
580 |
// L: a lock in limbo, |
|
581 |
// Q: a queued lock, |
|
582 |
// K: a lock kept across transaction end boundary, |
|
583 |
// J: a lock is part of a JTA transaction, |
|
584 |
// C: a lock is in create mode for JTA, |
|
585 |
// E: a lock wait timeout has expired on this queued lock. |
|
586 |
proxy.setLockFlags(new character(code)); |
|
587 |
|
|
588 |
boolean isTable = (event.getIdentifier() instanceof TableIdentifier); |
|
589 |
proxy.setLockType(new character(isTable ? "TAB" : "REC")); |
|
590 |
|
|
591 |
persistence.save(dmo, dmo.primaryKey()); |
|
592 |
} |
|
593 |
|
|
594 |
persistence.flush(); |
|
595 |
|
|
596 |
if (commit) |
|
597 |
{ |
|
598 |
commit = false; // to avoid double-rollback in catch block if commit fails |
|
599 |
persistence.commit(); |
|
600 |
} |
|
601 |
|
|
602 |
if (log.isLoggable(Level.FINEST)) |
|
603 |
{ |
|
604 |
log.log(Level.FINEST, event.toString()); |
|
605 |
} |
|
606 |
} |
|
607 |
catch (Exception exc) |
|
608 |
{ |
|
609 |
try |
|
610 |
{ |
|
611 |
if (commit) |
|
612 |
{ |
|
613 |
persistence.rollback(); |
|
614 |
} |
|
615 |
} |
|
616 |
catch (PersistenceException pe) |
|
617 |
{ |
|
618 |
// error already logged in finally block |
|
619 |
} |
|
620 |
finally |
|
621 |
{ |
|
622 |
if (log.isLoggable(Level.SEVERE)) |
|
623 |
{ |
|
624 |
String msg = String.format("Error updating _lock table for %s (%s)", |
|
625 |
persistence.getDatabase().toString(), |
|
626 |
event.toString()); |
|
627 |
log.log(Level.SEVERE, msg, exc); |
|
628 |
} |
|
445 |
mapVersion.incrementAndGet(); |
|
446 |
LockRecId lrId = new LockRecId(event); |
|
447 |
if (event.getNewType() == LockType.NONE ) |
|
448 |
{ |
|
449 |
MinimalLockImpl ml = locks.remove(lrId); |
|
450 |
if (ml != null && ml.persisted.get() >= 0) |
|
451 |
{ |
|
452 |
// lock was already persisted, VST cleanup is required |
|
453 |
ml.guardedUpdate(event); |
|
454 |
executor.execute(() -> { |
|
455 |
try |
|
456 |
{ |
|
457 |
if (ml.persist()) |
|
458 |
{ |
|
459 |
deletesFromVST.incrementAndGet(); |
|
460 |
} |
|
461 |
} |
|
462 |
catch(Throwable t) |
|
463 |
{ |
|
464 |
log.log(Level.WARNING, "Cannot delete lock data for " + ml.eventString, t); |
|
465 |
} |
|
466 |
}); |
|
467 |
} |
|
468 |
return; |
|
469 |
} |
|
470 |
MinimalLockImpl ml = locks.computeIfAbsent(lrId, k -> new MinimalLockImpl(event)); |
|
471 |
if (!ml.version.compareAndSet(-1, 0)) // not a new lock, should be updated |
|
472 |
{ |
|
473 |
ml.guardedUpdate(event); |
|
629 | 474 |
} |
630 | 475 |
} |
631 | 476 |
finally |
632 | 477 |
{ |
633 |
if (dmo != null) |
|
634 |
{ |
|
635 |
RecordIdentifier<String> metaIdent = new RecordIdentifier<>(dmoClass.getName(), |
|
636 |
dmo.primaryKey()); |
|
637 |
try |
|
638 |
{ |
|
639 |
persistence.lock(LockType.NONE, metaIdent, true); |
|
640 |
} |
|
641 |
catch (LockUnavailableException exc) |
|
642 |
{ |
|
643 |
// won't be thrown from lock release |
|
644 |
} |
|
645 |
} |
|
478 |
guard.readLock().unlock(); |
|
646 | 479 |
} |
647 | 480 |
} |
648 | 481 |
|
... | ... | |
655 | 488 |
* |
656 | 489 |
* @return See above. |
657 | 490 |
*/ |
658 |
private long getLockRecordID(RecordLockEvent event) |
|
659 |
throws PersistenceException |
|
491 |
public long getLockRecordID(RecordLockEvent event) |
|
660 | 492 |
{ |
661 | 493 |
Long recordID = event.getIdentifier().getRecordID(); |
662 | 494 |
long recID = (recordID != null) ? (Long) recordID : -getFileNum(event); |
... | ... | |
665 | 497 |
} |
666 | 498 |
|
667 | 499 |
/** |
668 |
* Find the metadata lock record associated with the user ID and lock record ID specified by |
|
669 |
* the given record lock event. |
|
670 |
* |
|
671 |
* @param event |
|
672 |
* Record lock event. |
|
673 |
* |
|
674 |
* @return Metadata lock record, if found. |
|
675 |
* |
|
676 |
* @throws PersistenceException |
|
677 |
* if a lock record cannot be found for the given event, or if there is an error |
|
678 |
* executing the query. |
|
679 |
*/ |
|
680 |
private Record findRecord(RecordLockEvent event) |
|
681 |
throws PersistenceException |
|
682 |
{ |
|
683 |
long lockRec = getLockRecordID(event); |
|
684 |
int lockUsr = event.getUserid().intValue(); |
|
685 |
Object[] args = new Object[] { lockRec, lockUsr }; |
|
686 |
|
|
687 |
Long id = persistence.getSingleSQLResult(SQL_LOCK, args); |
|
688 |
Record dmo = null; |
|
689 |
|
|
690 |
if (id != null) |
|
691 |
{ |
|
692 |
RecordIdentifier<String> metaIdent = new RecordIdentifier<>(dmoClass.getName(), id); |
|
693 |
persistence.lock(LockType.EXCLUSIVE, metaIdent, true); |
|
694 |
dmo = persistence.quickLoad(metaIdent); |
|
695 |
} |
|
696 |
|
|
697 |
if (dmo == null) |
|
698 |
{ |
|
699 |
throw new PersistenceException("No lock record found for lock event: " + event); |
|
700 |
} |
|
701 |
|
|
702 |
handler.setDelegate(dmo); |
|
703 |
|
|
704 |
return dmo; |
|
705 |
} |
|
706 |
|
|
707 |
/** |
|
708 |
* Create a metadata lock record for the given record lock event. |
|
709 |
* |
|
710 |
* @param event |
|
711 |
* Event which specifies information to be stored in the lock record. |
|
712 |
* |
|
713 |
* @return New lock record. |
|
714 |
* |
|
715 |
* @throws PersistenceException |
|
716 |
* if there is any error querying related metadata, or if there is a reflection |
|
717 |
* error instantiating the DMO. |
|
718 |
*/ |
|
719 |
private Record createRecord(RecordLockEvent event) |
|
720 |
throws PersistenceException |
|
721 |
{ |
|
722 |
Record dmo = null; |
|
723 |
|
|
724 |
try |
|
725 |
{ |
|
726 |
// create new DMO instance and back the proxy with it |
|
727 |
dmo = dmoClass.newInstance(); |
|
728 |
dmo.initialize(null, true); |
|
729 |
handler.setDelegate(dmo); |
|
730 |
|
|
731 |
// assign primary key |
|
732 |
Long key = nextPrimaryKey++; |
|
733 |
dmo.primaryKey(key); |
|
734 |
|
|
735 |
// get file number for table |
|
736 |
Integer fileNum = getFileNum(event); |
|
737 |
|
|
738 |
// lock meta record for writing |
|
739 |
RecordIdentifier<String> metaIdent = new RecordIdentifier<>(dmoClass.getName(), dmo.primaryKey()); |
|
740 |
persistence.lock(LockType.EXCLUSIVE, metaIdent, true); |
|
741 |
|
|
742 |
// set user name and user id |
|
743 |
String subject = event.getOsUserName(); |
|
744 |
if (subject == null) |
|
745 |
{ |
|
746 |
subject = event.getLocker().getSubject(); |
|
747 |
} |
|
748 |
character userName = new character(subject); |
|
749 |
proxy.setLockName(userName); |
|
750 |
proxy.setLockUsr(event.getUserid()); |
|
751 |
|
|
752 |
// assign other properties that will not change with new events |
|
753 |
proxy.setLockId(new int64(key)); // re-use primary key |
|
754 |
proxy.setLockTable(new integer(fileNum)); |
|
755 |
proxy.setLockRecid(new int64(getLockRecordID(event))); |
|
756 |
|
|
757 |
// In ProgressOE, the locking table is divided into chains anchored in a hash table. |
|
758 |
// These chains are provided for fast lookup of record locks by Rec-id. |
|
759 |
// FWD OTOH, relies on underlying database for recid/rowid lookup so the chain is at |
|
760 |
// least irrelevant. In order to have a substitute and keep things simple, we will |
|
761 |
// assume that each table has a single chain and its value is computed as an fixed |
|
762 |
// offset from the table ID. |
|
763 |
proxy.setLockChain(new integer(fileNum + CHAIN_OFFSET)); |
|
764 |
} |
|
765 |
catch (InstantiationException | IllegalAccessException exc) |
|
766 |
{ |
|
767 |
String msg = String.format("Error creating meta lock record for event %s", |
|
768 |
event.toString()); |
|
769 |
|
|
770 |
throw new PersistenceException(msg, exc); |
|
771 |
} |
|
772 |
|
|
773 |
return dmo; |
|
774 |
} |
|
775 |
|
|
776 |
/** |
|
777 | 500 |
* Get the file number associated with a lock event. |
778 | 501 |
* |
779 | 502 |
* @param event |
... | ... | |
782 | 505 |
* @return File number for the table associated with the event. |
783 | 506 |
*/ |
784 | 507 |
private Integer getFileNum(RecordLockEvent event) |
785 |
throws PersistenceException |
|
786 | 508 |
{ |
787 | 509 |
RecordIdentifier<String> ident = event.getIdentifier(); |
788 | 510 |
String table = ident.getTable(); |
789 |
Integer fileNum = fileNums.get(table);
|
|
790 |
if (fileNum == null)
|
|
511 |
Integer fileNum; |
|
512 |
if (MetadataManager.inUse(MetadataManager._FILE))
|
|
791 | 513 |
{ |
792 |
if (MetadataManager.inUse(MetadataManager._FILE)) |
|
793 |
{ |
|
794 |
// converted table name is stored in MetaFile.userMisc |
|
795 |
Object[] args = new Object[] { table }; |
|
796 |
fileNum = persistence.getSingleSQLResult(SQL_FILENUM, args); |
|
797 |
|
|
798 |
if (fileNum == null) |
|
799 |
{ |
|
800 |
String msg = "Table '" + table + "' not found in metadata [" + event.toString() + "]"; |
|
801 |
throw new PersistenceException(msg); |
|
802 |
} |
|
803 |
} |
|
804 |
else |
|
805 |
{ |
|
806 |
// the _File metadata is not available |
|
807 |
fileNum = 0; |
|
808 |
} |
|
514 |
// converted table name is stored in MetaFile.userMisc |
|
515 |
fileNum = MetadataManager.getFileNum(primary, table); |
|
809 | 516 |
|
810 |
// cache this to avoid future query by userMisc, which is not indexed |
|
811 |
fileNums.put(table, fileNum); |
|
812 |
} |
|
813 |
|
|
517 |
if (fileNum == null) |
|
518 |
{ |
|
519 |
String msg = "Table '" + table + "' not found in metadata [" + event.toString() + "]"; |
|
520 |
throw new IllegalStateException(msg); |
|
521 |
} |
|
522 |
} |
|
523 |
else |
|
524 |
{ |
|
525 |
// the _File metadata is not available |
|
526 |
fileNum = 0; |
|
527 |
} |
|
814 | 528 |
return fileNum; |
815 | 529 |
} |
816 | 530 |
|
... | ... | |
855 | 569 |
this.delegate = delegate; |
856 | 570 |
} |
857 | 571 |
} |
572 | ||
573 |
/** In-memory holder for the _Lock VST data*/ |
|
574 |
private class MinimalLockImpl |
|
575 |
{ |
|
576 |
/** Lock guard */ |
|
577 |
public final ReentrantReadWriteLock guard = new ReentrantReadWriteLock(); |
|
578 |
|
|
579 |
/** Lock version */ |
|
580 |
public final AtomicLong version = new AtomicLong(-1); |
|
581 |
|
|
582 |
/** Lock version last persisted */ |
|
583 |
public final AtomicLong persisted = new AtomicLong(-1); |
|
584 |
|
|
585 |
/** Flag indicating that the corresponding record was already deleted in VST */ |
|
586 |
public volatile boolean deleted = false; |
|
587 |
|
|
588 |
/** Primary key */ |
|
589 |
public final long key; |
|
590 |
|
|
591 |
/** Lock Id */ |
|
592 |
public final int64 lockId; |
|
593 |
|
|
594 |
/** User */ |
|
595 |
public final integer lockUsr; |
|
596 |
|
|
597 |
/** Lock name */ |
|
598 |
public final character lockName; |
|
599 |
|
|
600 |
/** Table fileNum */ |
|
601 |
public final integer lockTable; |
|
602 |
|
|
603 |
/** Record Id */ |
|
604 |
public final int64 lockRecid; |
|
605 |
|
|
606 |
/** Chain */ |
|
607 |
public final integer lockChain; |
|
608 | ||
609 |
/** Old lock type */ |
|
610 |
private LockType oldType; |
|
611 |
|
|
612 |
/** New lock type */ |
|
613 |
private LockType newType; |
|
614 |
|
|
615 |
/** Lock type string */ |
|
616 |
private character lockType; |
|
617 |
|
|
618 |
/** Lock flags */ |
|
619 |
private character lockFlags; |
|
620 |
|
|
621 |
/** Event string representation (for logging) */ |
|
622 |
private String eventString; |
|
623 |
|
|
624 |
/** |
|
625 |
* Constructor |
|
626 |
* |
|
627 |
* @param event |
|
628 |
* Lock event |
|
629 |
*/ |
|
630 |
public MinimalLockImpl(RecordLockEvent event) |
|
631 |
{ |
|
632 |
key = nextPrimaryKey.incrementAndGet(); |
|
633 | ||
634 |
// set user name and user id |
|
635 |
String subject = event.getOsUserName(); |
|
636 |
if (subject == null) |
|
637 |
{ |
|
638 |
subject = event.getLocker().getSubject(); |
|
639 |
} |
|
640 |
character userName = new character(subject); |
|
641 |
lockName = userName; |
|
642 |
lockUsr = event.getUserid(); |
|
643 |
|
|
644 |
// get file number for table |
|
645 |
Integer fileNum = getFileNum(event); |
|
646 | ||
647 |
// assign other properties that will not change with new events |
|
648 |
lockId = new int64(key); // re-use primary key |
|
649 |
lockTable = new integer(fileNum); |
|
650 |
lockRecid = new int64(getLockRecordID(event)); |
|
651 |
|
|
652 |
// In ProgressOE, the locking table is divided into chains anchored in a hash table. |
|
653 |
// These chains are provided for fast lookup of record locks by Rec-id. |
|
654 |
// FWD OTOH, relies on underlying database for recid/rowid lookup so the chain is at |
|
655 |
// least irrelevant. In order to have a substitute and keep things simple, we will |
|
656 |
// assume that each table has a single chain and its value is computed as an fixed |
|
657 |
// offset from the table ID. |
|
658 |
lockChain = new integer(fileNum + CHAIN_OFFSET); |
|
659 |
|
|
660 |
update(event); |
|
661 |
} |
|
662 |
|
|
663 |
/** |
|
664 |
* Guarded update holder with event data |
|
665 |
* @param event |
|
666 |
* lock event |
|
667 |
* |
|
668 |
*/ |
|
669 |
public void guardedUpdate(RecordLockEvent event) |
|
670 |
{ |
|
671 |
guard.writeLock().lock(); |
|
672 |
try |
|
673 |
{ |
|
674 |
update(event); |
|
675 |
version.incrementAndGet(); |
|
676 |
} |
|
677 |
finally |
|
678 |
{ |
|
679 |
guard.writeLock().unlock(); |
|
680 |
} |
|
681 |
} |
|
682 | ||
683 |
/** |
|
684 |
* Update holder with event data |
|
685 |
* @param event |
|
686 |
* lock event |
|
687 |
* |
|
688 |
*/ |
|
689 |
public void update(RecordLockEvent event) |
|
690 |
{ |
|
691 |
this.oldType = event.getOldType(); |
|
692 |
this.newType = event.getNewType(); |
|
693 |
String code = newType.isExclusive() ? "X" : "S"; // TODO: get this right |
|
694 |
if (oldType.isShare()) |
|
695 |
{ |
|
696 |
code += " U"; // an upgrade |
|
697 |
} |
|
698 |
// From ABL online manual: values of flags field specify: |
|
699 |
// S: a share lock, |
|
700 |
// X: an exclusive lock, |
|
701 |
// U: a lock upgraded from share to exclusive, |
|
702 |
// L: a lock in limbo, |
|
703 |
// Q: a queued lock, |
|
704 |
// K: a lock kept across transaction end boundary, |
|
705 |
// J: a lock is part of a JTA transaction, |
|
706 |
// C: a lock is in create mode for JTA, |
|
707 |
// E: a lock wait timeout has expired on this queued lock. |
|
708 |
this.lockFlags = new character(code); |
|
709 |
|
|
710 |
boolean isTable = (event.getIdentifier() instanceof TableIdentifier); |
|
711 |
this.lockType = new character(isTable ? "TAB" : "REC"); |
|
712 |
this.eventString = String.format("%s: %d[%d, %d]", event.toString(), |
|
713 |
key, lockRecid.longValue(), lockUsr.intValue()); |
|
714 |
} |
|
715 |
|
|
716 |
/** Create record in the _Lock VST */ |
|
717 |
private Record createRecord() throws PersistenceException |
|
718 |
{ |
|
719 |
Record dmo = null; |
|
720 | ||
721 |
try |
|
722 |
{ |
|
723 |
// create new DMO instance and back the proxy with it |
|
724 |
dmo = dmoClass.newInstance(); |
|
725 |
dmo.initialize(null, true); |
|
726 |
handler.setDelegate(dmo); |
|
727 | ||
728 |
// assign primary key |
|
729 |
dmo.primaryKey(key); |
|
730 |
// lock meta record for writing |
|
731 |
RecordIdentifier<String> metaIdent = new RecordIdentifier<>(dmoClass.getName(), |
|
732 |
dmo.primaryKey()); |
|
733 |
persistence.lock(LockType.EXCLUSIVE, metaIdent, true); |
|
734 |
proxy.setLockName(lockName); |
|
735 |
proxy.setLockUsr(lockUsr); |
|
736 |
proxy.setLockId(lockId); // re-use primary key |
|
737 |
proxy.setLockTable(lockTable); |
|
738 |
proxy.setLockRecid(lockRecid); |
|
739 |
proxy.setLockChain(lockChain); |
|
740 |
} |
|
741 |
catch (InstantiationException | IllegalAccessException exc) |
|
742 |
{ |
|
743 |
String msg = String.format("Error creating meta lock record for event %s", |
|
744 |
eventString); |
|
745 | ||
746 |
throw new PersistenceException(msg, exc); |
|
747 |
} |
|
748 |
return dmo; |
|
749 |
} |
|
750 |
|
|
751 | ||
752 |
/** Find record in the _Lock VST */ |
|
753 |
private Record findRecord() throws PersistenceException |
|
754 |
{ |
|
755 |
Long id = persistence.getSingleSQLResult(SQL_LOCK, new Object[] { key }); |
|
756 |
Record dmo = null; |
|
757 | ||
758 |
if (id != null) |
|
759 |
{ |
|
760 |
RecordIdentifier<String> metaIdent = new RecordIdentifier<>(dmoClass.getName(), |
|
761 |
id); |
|
762 |
persistence.lock(LockType.EXCLUSIVE, metaIdent, true); |
|
763 |
dmo = persistence.quickLoad(metaIdent); |
|
764 |
} |
|
765 | ||
766 |
if (dmo == null) |
|
767 |
{ |
|
768 |
throw new PersistenceException( |
|
769 |
"No lock record found for lock event: " + eventString); |
|
770 |
} |
|
771 | ||
772 |
handler.setDelegate(dmo); |
|
773 | ||
774 |
return dmo; |
|
775 |
} |
|
776 | ||
777 |
/** |
|
778 |
* Guarded persist lock data in the _Lock VST. |
|
779 |
* @return <code>true</code> if success |
|
780 |
*/ |
|
781 |
public boolean persist() |
|
782 |
{ |
|
783 |
guard.writeLock().lock(); |
|
784 |
try |
|
785 |
{ |
|
786 |
return doPersist(); |
|
787 |
} |
|
788 |
finally |