Project

General

Profile

_lock2.diff

Igor Skornyakov, 04/08/2022 05:03 PM

Download (63.5 KB)

View differences:

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
... This diff was truncated because it exceeds the maximum size that can be displayed.