Project

General

Profile

_lock.diff

Igor Skornyakov, 03/24/2022 03:07 PM

Download (43.3 KB)

View differences:

src/com/goldencode/p2j/persist/DatabaseManager.java 2022-03-24 18:34:39 +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-03-24 18:38:39 +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
*/
1202 1203

  
1203 1204
/*
......
6695 6696
         ErrorManager.recordOrShowError(14378, msg, true);
6696 6697
      }
6697 6698
      
6698
      if (legacyName.equalsIgnoreCase("_Lock"))
6699
      {
6700
         persistence.flushMetaLockEvents();
6701
      } 
6702
      else if (legacyName.equalsIgnoreCase("_Trans"))
6699
      if (legacyName.equalsIgnoreCase("_Trans"))
6703 6700
      {
6704 6701
         // flush the transaction id to persistence if required
6705 6702
         TransactionManager.flushTransMetaData(getPersistence());
src/com/goldencode/p2j/persist/meta/LockTableUpdater.java 2022-03-24 18:58:01 +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

  
87 91
import com.goldencode.p2j.persist.*;
......
168 172
   
169 173
   /**
170 174
    * Constructor.
171
    * 
175
    * @param   primary 
176
    *          Primary database
172 177
    * @param   metaDatabase
173 178
    *          Metadata database.
174 179
    */
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
   
180
   public LockTableUpdater(Database primary, Database metaDatabase)
181
   {
182
      this.worker = new UpdateWorker(
183
                        PersistenceFactory.getInstance(metaDatabase),
184
                        primary);
185
   }
186
   
187
   /**
188
    * Persist locks in the meta database
189
    */
190
   public void persist()
191
   {
192
      worker.persist();
193
   }
201 194
   /**
202 195
    * Method which is called by a {@link LockManager} in response to a lock event. Does some
203 196
    * preliminary processing and sanity checking, then queues the event for further processing on
......
244 237
    * thread processes the events.
245 238
    */
246 239
   private static class UpdateWorker
247
   implements Runnable
248 240
   {
249 241
      /** Metadata lock table name */
250 242
      private static final String LOCK_TABLE = "meta_lock";
......
304 296
         dmoClass = implementingClass;
305 297
      }
306 298
      
307
      /** Event queue to which many threads can write and from which only one thread reads */
308
      private final List<RecordLockEvent> eventQueue = new LinkedList<>();
299
      /** Locks' map */
300
      private final ConcurrentMap<LockRecId, MinimalLockImpl> locks = new ConcurrentSkipListMap<>();
301

  
302
      /** Map version */
303
      private final AtomicLong mapVersion = new AtomicLong();
304

  
305
      /** Meta table vesrion */
306
      private final AtomicLong databaseVersion = new AtomicLong(0);
307

  
308
      /** Locks' map guard */
309
      private final ReentrantReadWriteLock guard = new ReentrantReadWriteLock();
309 310
      
311
      /** database name */
312
      private final Database primary;
313

  
310 314
      /** Persistence helper object for metadata database */
311 315
      private final Persistence persistence;
312 316
      
......
316 320
      /** Invocation handler which maps proxy method calls to underlying DMO methods */
317 321
      private final Handler handler = new Handler();
318 322
      
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 323
      /** Next available primary key for a new lock metadata record */
341
      private long nextPrimaryKey = 1L;
324
      private AtomicLong nextPrimaryKey = new AtomicLong(1L);
342 325
      
343 326
      /** Dummy empty class to be used as the super class for proxy. */
344
      public static class Empty {}
327
      public static final class Empty {}
345 328
      
346 329
      /**
347 330
       * Constructor.
......
349 332
       * @param   persistence
350 333
       *          Persistence helper object for metadata database.
351 334
       */
352
      UpdateWorker(Persistence persistence)
335
      UpdateWorker(Persistence persistence, Database primary)
353 336
      {
354 337
         super();
355 338
         
356 339
         this.persistence = persistence;
340
         this.primary = primary;
357 341
         this.proxy = ProxyFactory.getProxy(Empty.class, new Class<?>[] { MinimalLock.class }, handler);
358 342
         
359
         workThread = new AssociatedThread(this);
360
         workThread.setDaemon(true);
361
         workThread.setName("Lock metadata update thread");
362
         workThread.start();
363 343
      }
364 344
      
365 345
      /**
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.
346
       * Persist locks in the meta database
368 347
       */
369
      @Override
370
      public void run()
348
      public void persist()
371 349
      {
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
         }
350
         guard.writeLock().lock();
351
         try
352
         {
353
            if (databaseVersion.get() >= mapVersion.get())
354
            {
355
               return;
356
            }
357
            for(Iterator<Map.Entry<LockRecId, MinimalLockImpl>> it = locks.entrySet().iterator();
358
                it.hasNext(); )
359
            {
360
               Map.Entry<LockRecId, MinimalLockImpl> e = it.next();
361
               if (e.getValue().persist())
362
               {
363
                  it.remove();
364
               }
365
            }
366
            databaseVersion.set(mapVersion.get());
367
         }
368
         finally
369
         {
370
            guard.writeLock().unlock();
371
         }
372
         
403 373
      }
404
      
405 374
      /**
406 375
       * Add a record lock event to the queue, locking out other write and read operations until
407 376
       * the queue is updated. Notify a the worker thread when the new event has been added.
......
411 380
       */
412 381
      void addEvent(RecordLockEvent event)
413 382
      {
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
         
383
         guard.readLock().lock();
548 384
         try
549 385
         {
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
               }
629
            }
386
            LockRecId lrId = new LockRecId(event);
387
            MinimalLockImpl ml = locks.computeIfAbsent(lrId, k -> new MinimalLockImpl(event));
388
            if (!ml.version.compareAndSet(-1, 0)) // not a new lock, should be updated
389
            {
390
               ml.guard.writeLock().lock();
391
               try
392
               {
393
                  ml.update(event);
394
                  ml.version.incrementAndGet();
395
               }
396
               finally
397
               {
398
                  ml.guard.writeLock().unlock();
399
               }
400
            }
401
            mapVersion.incrementAndGet();
630 402
         }
631 403
         finally
632 404
         {
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
            }
405
            guard.readLock().unlock();
646 406
         }
647 407
      }
648 408
      
......
655 415
       * 
656 416
       * @return  See above.
657 417
       */
658
      private long getLockRecordID(RecordLockEvent event)
659
      throws PersistenceException
418
      public long getLockRecordID(RecordLockEvent event) 
660 419
      {
661 420
         Long recordID = event.getIdentifier().getRecordID();
662 421
         long recID = (recordID != null) ? (Long) recordID : -getFileNum(event);
......
665 424
      }
666 425
      
667 426
      /**
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 427
       * Get the file number associated with a lock event.
778 428
       * 
779 429
       * @param   event
......
782 432
       * @return  File number for the table associated with the event.
783 433
       */
784 434
      private Integer getFileNum(RecordLockEvent event)
785
      throws PersistenceException
786 435
      {
787 436
         RecordIdentifier<String> ident = event.getIdentifier();
788 437
         String table = ident.getTable();
789
         Integer fileNum = fileNums.get(table);
790
         if (fileNum == null)
438
         Integer fileNum;
439
         if (MetadataManager.inUse(MetadataManager._FILE))
791 440
         {
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
            }
441
            // converted table name is stored in MetaFile.userMisc
442
            fileNum = MetadataManager.getFileNum(primary, table);
809 443
            
810
            // cache this to avoid future query by userMisc, which is not indexed
811
            fileNums.put(table, fileNum);
812
         }
813
         
444
            if (fileNum == null)
445
            {
446
               String msg = "Table '" + table + "' not found in metadata [" + event.toString() + "]";
447
               throw new IllegalStateException(msg);
448
            }
449
         }
450
         else
451
         {
452
            // the _File metadata is not available
453
            fileNum = 0;
454
         }
814 455
         return fileNum;
815 456
      }
816 457
      
......
855 496
            this.delegate = delegate;
856 497
         }
857 498
      }
499

  
500
      /** In-memory holder for the _Lock VST data*/ 
501
      private class MinimalLockImpl
502
      {
503
         /** Lock guard */
504
         public final ReentrantReadWriteLock guard = new ReentrantReadWriteLock();
505
         /** Lock version */
506
         public final AtomicLong version = new AtomicLong(-1);
507
         /** Lock version last persisted */
508
         public final AtomicLong persisted = new AtomicLong(-1);
509
         
510
         /** Primary key */
511
         public final long key;
512
         
513
         /** Lock Id */
514
         public final int64 lockId;
515
         
516
         /** User */
517
         public final integer lockUsr;
518
         
519
         /** Lock name */
520
         public final character lockName;
521
         
522
         /** Table fileNum */ 
523
         public final integer lockTable;
524
         
525
         /** Record Id */
526
         public final int64 lockRecid;
527
         
528
         /** Chain */
529
         public final integer lockChain;
530

  
531
         /** Old lock type */
532
         private LockType oldType;
533
         
534
         /** New lock type */
535
         private LockType newType;
536
         
537
         /** Lock type string */
538
         private character lockType;
539
         
540
         /** Lock flags */
541
         private character lockFlags;
542
         
543
         /** Event string representation (for logging) */
544
         private String eventString;
545
         
546
         /**
547
          * Constructor
548
          * 
549
          * @param event
550
          *        Lock event
551
          */
552
         public MinimalLockImpl(RecordLockEvent event)
553
         {
554
            key = nextPrimaryKey.incrementAndGet();
555

  
556
            // set user name and user id
557
            String subject = event.getOsUserName();
558
            if (subject == null)
559
            {
560
               subject = event.getLocker().getSubject();
561
            }
562
            character userName = new character(subject);
563
            lockName = userName;
564
            lockUsr = event.getUserid();
565
            
566
            // get file number for table
567
            Integer fileNum = getFileNum(event);
568

  
569
            // assign other properties that will not change with new events
570
            lockId = new int64(key);    // re-use primary key
571
            lockTable = new integer(fileNum);
572
            lockRecid = new int64(getLockRecordID(event));
573
            
574
            // In ProgressOE, the locking table is divided into chains anchored in a hash table.
575
            // These chains are provided for fast lookup of record locks by Rec-id.
576
            // FWD OTOH, relies on underlying database for recid/rowid lookup so the chain is at
577
            // least irrelevant. In order to have a substitute and keep things simple, we will
578
            // assume that each table has a single chain and its value is computed as an fixed
579
            // offset from the table ID.
580
            lockChain = new integer(fileNum + CHAIN_OFFSET);
581
            
582
            update(event);
583
         }
584
         
585
         /** 
586
          * Update holder with event data
587
          * @param event 
588
          *        lock event
589
          *  
590
          */ 
591
         public void update(RecordLockEvent event)
592
         {
593
            this.oldType = event.getOldType();
594
            this.newType = event.getNewType();
595
            String code = newType.isExclusive() ? "X" : "S"; // TODO:  get this right
596
            if (oldType.isShare())
597
            {
598
               code += " U"; // an upgrade
599
            }
600
            // From ABL online manual: values of flags field specify:
601
            //    S: a share lock,
602
            //    X: an exclusive lock,
603
            //    U: a lock upgraded from share to exclusive,
604
            //    L: a lock in limbo,
605
            //    Q: a queued lock,
606
            //    K: a lock kept across transaction end boundary,
607
            //    J: a lock is part of a JTA transaction,
608
            //    C: a lock is in create mode for JTA,
609
            //    E: a lock wait timeout has expired on this queued lock.
610
            this.lockFlags = new character(code);
611
            
612
            boolean isTable = (event.getIdentifier() instanceof TableIdentifier);
613
            this.lockType = new character(isTable ? "TAB" : "REC");
614
            this.eventString = event.toString();
615
         }
616
         
617
         /** Create record in the _Lock VST */
618
         private Record createRecord() throws PersistenceException
619
         {
620
            Record dmo = null;
621

  
622
            try
623
            {
624
               // create new DMO instance and back the proxy with it
625
               dmo = dmoClass.newInstance();
626
               dmo.initialize(null, true);
627
               handler.setDelegate(dmo);
628

  
629
               // assign primary key
630
               dmo.primaryKey(key);
631
               // lock meta record for writing
632
               RecordIdentifier<String> metaIdent = new RecordIdentifier<>(dmoClass.getName(),
633
                        dmo.primaryKey());
634
               persistence.lock(LockType.EXCLUSIVE, metaIdent, true);
635
               proxy.setLockName(lockName);
636
               proxy.setLockUsr(lockUsr);
637
               proxy.setLockId(lockId); // re-use primary key
638
               proxy.setLockTable(lockTable);
639
               proxy.setLockRecid(lockRecid);
640
               proxy.setLockChain(lockChain);
641
            }
642
            catch (InstantiationException | IllegalAccessException exc)
643
            {
644
               String msg = String.format("Error creating meta lock record for event %s",
645
                        eventString);
646

  
647
               throw new PersistenceException(msg, exc);
648
            }
649
            return dmo;
650
         }
651
         
652

  
653
         /** Find record in the _Lock VST */
654
         private Record findRecord() throws PersistenceException
655
         {
656
            Object[] args = new Object[] { lockRecid, lockUsr };
657

  
658
            Long id = persistence.getSingleSQLResult(SQL_LOCK, args);
659
            Record dmo = null;
660

  
661
            if (id != null)
662
            {
663
               RecordIdentifier<String> metaIdent = new RecordIdentifier<>(dmoClass.getName(),
664
                        id);
665
               persistence.lock(LockType.EXCLUSIVE, metaIdent, true);
666
               dmo = persistence.quickLoad(metaIdent);
667
            }
668

  
669
            if (dmo == null)
670
            {
671
               throw new PersistenceException(
672
                        "No lock record found for lock event:  " + eventString);
673
            }
674

  
675
            handler.setDelegate(dmo);
676

  
677
            return dmo;
678
         }
679

  
680
         /** 
681
          * Persist lock data in the _Lock VST 
682
          * @return <code>true</true> if the record is deleted
683
          */
684
         public boolean persist()
685
         {
686
            
687
            boolean deleted = false;
688
            if (persisted.get() >= version.get())
689
            {
690
               return deleted;
691
            }
692
            Record dmo = null;
693
            boolean commit = false;
694
            
695
            try
696
            {
697
               commit = persistence.beginTransaction();
698
               
699
               if (LockType.NONE.equals(oldType))
700
               {
701
                  dmo = createRecord();
702
               }
703
               else
704
               {
705
                  dmo = findRecord();
706
                  if (LockType.NONE.equals(newType))
707
                  {
708
                     persistence.delete(dmo);
709
                     deleted = true;
710
                  }
711
                  else
712
                  {
713
                     proxy.setLockFlags(lockFlags);
714
                     proxy.setLockType(lockType);
715
                     persistence.save(dmo, dmo.primaryKey());
716
                  }
717
               }
718

  
719
               persistence.flush();
720
               
721
               if (commit)
722
               {
723
                  commit = false;   // to avoid double-rollback in catch block if commit fails
724
                  persistence.commit();
725
               }
726
               persisted.set(version.get());
727
            }
728
            catch (Exception exc)
729
            {
730
               try
731
               {
732
                  if (commit)
733
                  {
734
                     persistence.rollback();
735
                  }
736
               }
737
               catch (PersistenceException pe)
738
               {
739
                  // error already logged in finally block
740
               }
741
               finally
742
               {
743
                  if (log.isLoggable(Level.SEVERE))
744
                  {
745
                     String msg = String.format("Error updating _lock table for %s (%s)",
746
                                                persistence.getDatabase().toString(),
747
                                                eventString);
748
                     log.log(Level.SEVERE, msg, exc);
749
                  }
750
               }
751
            }
752
            finally
753
            {
754
               if (dmo != null)
755
               {
756
                  RecordIdentifier<String> metaIdent = new RecordIdentifier<>(dmoClass.getName(),
757
                                                                              dmo.primaryKey());
758
                  try
759
                  {
760
                     persistence.lock(LockType.NONE, metaIdent, true);
761
                  }
762
                  catch (LockUnavailableException exc)
763
                  {
764
                     // won't be thrown from lock release
765
                  }
766
               }
767
            }
768
            return deleted;
769
         }
770
      }
771

  
772
      /** Lock record id */
773
      private class LockRecId
774
      implements Comparable<LockRecId>
775
      {
776
         public final long lockRec;
777
         public final int lockUsr;
778
         
779
         /**
780
          * Constructor
781
          * @param event
782
          *        Lock event
783
          */
784
         public LockRecId(RecordLockEvent event) 
785
         {
786
            this.lockRec = getLockRecordID(event);
787
            this.lockUsr = event.getUserid().intValue();
788
         }
789

  
790
         /**
791
          * Compare with ther record id
792
          * @return -1 if other record id is greater, 1 if other record is less, 0 if ids are equal
793
          */
794
         @Override
795
         public int compareTo(LockRecId o)
796
         {
797
            return this.lockRec < o.lockRec ? -1 : 
798
                   this.lockRec > o.lockRec ?  1 : 
799
                      this.lockUsr < o.lockUsr ? -1 :
800
                      this.lockUsr > o.lockUsr ?  1 : 0;
801
         }
802
         
803
      }
858 804
   }
859 805
   
860 806
   /**
......
986 932
       */
987 933
      public void setLockFlags(Text lockFlags);
988 934
   }
935
   
989 936
}
src/com/goldencode/p2j/persist/meta/MetadataManager.java 2022-03-24 18:40:04 +0000
39 39
**     CA  20210709 Set the _File.desc from the Table.description() annotation.
40 40
**     ECF 20210910 Code cleanup.
41 41
**     OM  20220301 Correctly set unknown value as initial value of the field.
42
*/
42
**     IAS 20220324 Added FILE_NUM_BY_TBL_NAME map.
43
**/
43 44

  
44 45
/*
45 46
** This program is free software: you can redistribute it and/or modify
......
301 302
   private static final ConcurrentMap<Database, ConcurrentMap<Class<?>, integer>> FILE_NUM =
302 303
         new ConcurrentHashMap<>();
303 304
   
305
   /** _File._File_num values for the primary databases by table name */
306
   private static final ConcurrentMap<Database, ConcurrentMap<String, Integer>> FILE_NUM_BY_TBL_NAME =
307
         new ConcurrentHashMap<>();
308

  
304 309
   /** Word tables' names for the primary databases */
305 310
   private static final ConcurrentMap<String, Map<String, Map<String, WordIndexData>>> WORD_TABLES =
306 311
         new ConcurrentHashMap<>();
......
353 358
   }
354 359
   
355 360
   /**
361
    * Get _File._File_num value for the table
362
    *  
363
    * @param db
364
    *        primary db
365
    * @param tableName
366
    *        Table nbame
367
    * @return _File._File_num value for the table
368
    */
369
   public static Integer getFileNum(Database db, String tableName)
370
   {
371
      return FILE_NUM_BY_TBL_NAME.computeIfAbsent(db, d -> new ConcurrentHashMap<>()).get(tableName);
372
   }
373

  
374
   /**
356 375
    * Initialize this class and start up a secure database server to allow remote P2J servers to
357 376
    * access metadata.
358 377
    * 
......
821 840
      templateRecords.put(dmoImpl, -fileNum);
822 841
      FILE_NUM.computeIfAbsent(primary, d -> new ConcurrentHashMap<>()).
823 842
            putIfAbsent(dmoImpl, new integer(fileNum));
843
      FILE_NUM_BY_TBL_NAME.computeIfAbsent(primary, d -> new ConcurrentHashMap<>()).
844
            putIfAbsent(dmoIface.getAnnotation(Table.class).name(), (int)fileNum);
824 845
      saveChildren(p, children);
825 846
   }
826 847
   
src/com/goldencode/p2j/persist/orm/FqlToSqlConverter.java 2022-03-24 18:37:44 +0000
30 30
**     IAS 20210905 Re-working re-writing queries with the values of the mutable SESSION attributes.
31 31
**     OM  20210908 Used SchemaDictionary.TEMP_TABLE_DB instead of hardcoded constant.
32 32
**     OM  20220212 Use ReservedProperty static method for testing reserved properties names.
33
**     IAS 20220324 Re-worked LockTableUpdater.
33 34
*/
34 35

  
35 36
/*
......
203 204
   /** Flag indicating that the query reads from _UserTableStat VST */
204 205
   private boolean userTableStatRead = false;
205 206

  
207
   /** Flag indicating that the query reads from _Lock VST */
208
   private boolean lockTableRead = false;
209

  
206 210
   /** Re-write data for CONTAINS operators */
207 211
   private final List<ContainsRewriteData> containsRewriteData = new ArrayList<>(); 
208 212
   
......
334 338
   }
335 339

  
336 340
   /**
341
    * Check if the query reads _Lock VST. 
342
    * 
343
    * @return <code>true</code> if the query reads _Lock VST.
344
    */
345
   public boolean isLockTableRead()
346
   {
347
      return lockTableRead;
348
   }
349

  
350
   /**
337 351
    * Re-write CONTAINS using word tables and CTE
338 352
    * 
339 353
    * @param   crwd
......
2881 2895
               {
2882 2896
                  userTableStatRead = true;
2883 2897
               }
2898
               if (dmoInfo.isVST(SystemTable._Lock))
2899
               {
2900
                  lockTableRead = true;
2901
               }
2884 2902
               if (alias == null || alias.getType() == ALIAS)
2885 2903
               {
2886 2904
                  String aliasName;
src/com/goldencode/p2j/persist/orm/Query.java 2022-03-24 18:38:09 +0000
18 18
**     IAS 20211223 Make getDialect(Session session) package private.
19 19
**     CA  20220304 Throw a IllegalStateException if the firstResult or maxResults parameter can't be set for
20 20
**                  the query.
21
**     IAS 20220324 Re-worked LockTableUpdater.
21 22
*/
22 23

  
23 24
/*
......
77 78

  
78 79
import java.util.*;
79 80
import java.util.function.*;
81

  
82

  
80 83
import com.goldencode.p2j.util.*;
81 84
import com.goldencode.p2j.persist.*;
82 85
import com.goldencode.p2j.persist.dialect.*;
......
102 105
   /** Flag indicating that the query reads from _UserTableStat VST */
103 106
   private boolean userTableStatRead = false;
104 107

  
108
   /** Flag indicating that the query reads from _Lock VST */
109
   private boolean lockTableRead = false;
110

  
105 111
   /**
106 112
    * The maximum number of results accepted by the (SELECT) query. Used with {@code firstResult}
107 113
    * to configure result pagination.
......
333 339
         paramCount = fql2sql.getLastConversionParamCount();
334 340
         wasRewritten = fql2sql.isQueryWasRewritten();
335 341
         userTableStatRead = fql2sql.isUserTableStatRead();
342
         lockTableRead = fql2sql.isLockTableRead();
336 343
         if (!wasRewritten)
337 344
         {
338 345
            cache.accept(this);
......
375 382
      {
376 383
         UserTableStatUpdater.persist(db);
377 384
      }
385
      if (lockTableRead)
386
      {
387
         LockTableUpdater ltu = DatabaseManager.getLockTableUpdater(db);
388
         if (ltu != null)
389
         {
390
            ltu.persist();
391
         }
392
         UserTableStatUpdater.persist(db);
393
      }
378 394
      return sqlQuery.expandPlaceholders();
379 395
   }
380 396