| 15 |
15 |
** the changes as other servers' (incrementRemoteIfTracked) and resolves the FastFindCache
|
| 16 |
16 |
** by database type and name (databaseType; the meta database has the primary's name).
|
| 17 |
17 |
** The per-database publish() is replaced by Pending; serialVersionUID 2.
|
|
18 |
** CA 20261029 Removed the off mode (isEnabled()). A server which may have missed a notice clears its
|
|
19 |
** fast-find caches and counts a remote change of every cached record (missed()): a receiver
|
|
20 |
** which failed to apply it; the servers which did not confirm it in time, or all others
|
|
21 |
** when the send failed, through the Missed function (resync()); the survivors of a server
|
|
22 |
** which departed (LockMembershipHandler). The receiver no longer marks Geode's thread.
|
|
23 |
** An answer to a notice renews the answering server's membership lease (Confirmations).
|
| 18 |
24 |
*/
|
| 19 |
25 |
|
| 20 |
26 |
/*
|
| ... | ... | |
| 105 |
111 |
* there has locked a changed record and read it again from the database. It must not make that current copy
|
| 106 |
112 |
* stale, or the session's update would not be saved: the receiver counts it apart from the local changes,
|
| 107 |
113 |
* and a session does not treat as stale a copy read under a lock its context still holds (see
|
| 108 |
|
* {@code Session.isStale}). A late notice therefore only delays NO-LOCK reads of other sessions there; a
|
| 109 |
|
* notice which is lost (not delivered, or not applied: the receiver answers -1) leaves their cached copies
|
| 110 |
|
* and FastFindCache entries of those records stale until they are evicted or change again. This
|
|
114 |
* {@code Session.isStale}). A late notice therefore only delays NO-LOCK reads of other sessions there.
|
|
115 |
* <p>
|
|
116 |
* A server which may have missed a notice clears all its FastFindCache results and counts a remote change
|
|
117 |
* of every record it caches ({@link #missed}), so nothing it cached before is used without reading it again:
|
|
118 |
* <ul>
|
|
119 |
* <li>a receiver which could not apply a notice, at once (it answers -1);</li>
|
|
120 |
* <li>the servers which did not confirm a notice in time (the sender's wait timed out, they left during
|
|
121 |
* it, or the wait failed), or every other server when the send itself failed: the sender tells them
|
|
122 |
* ({@link Missed}, without waiting; the servers to tell are coalesced while a call is pending); a
|
|
123 |
* server which is unresponsive gets it when it answers again, or Geode removes it from the view and
|
|
124 |
* its re-initialisation clears; a server which answers but cannot run it logs that;</li>
|
|
125 |
* <li>every server, when another FWD server departs (it may have committed without sending, or while
|
|
126 |
* sending, its notices), once the departed server can no longer commit ({@code LockMembershipHandler},
|
|
127 |
* after {@code MembershipLease.GRACE_MS});</li>
|
|
128 |
* <li>a server which lost its membership, in its re-initialisation ({@code GeodeConfig}).</li>
|
|
129 |
* </ul>
|
|
130 |
* In the batch and async modes a notice which is sent but lost without an error is not noticed. This
|
| 111 |
131 |
* needs a lock manager which tells since which value of the JVM-wide sequence (no clock) a lock is held
|
| 112 |
132 |
* ({@code LockManager.heldSinceSeq}): the owner-side lock manager, the cluster default; with another one a
|
| 113 |
133 |
* late notice can still drop a lock holder's update. Prerequisite of the cluster mode: the database runs at
|
| ... | ... | |
| 135 |
155 |
* to each other server, waited for (sync), or at most one per {@link #BATCH_MS} (batch); the message holds
|
| 136 |
156 |
* the changed records' ids. This replaces the FastFindCache's own broadcasts (several per transaction).
|
| 137 |
157 |
* <p>
|
| 138 |
|
* The mode is {@link #MODE}: {@code sync} (the default), {@code batch}, {@code async} (one call per
|
| 139 |
|
* transaction, not waited for) or {@code off}.
|
|
158 |
* The mode is {@link #MODE}: {@code sync} (the default), {@code batch} or {@code async} (one call per
|
|
159 |
* transaction, not waited for). The commit notice is the only invalidation of the FastFindCache across the
|
|
160 |
* servers.
|
| 140 |
161 |
*/
|
| 141 |
162 |
public final class CommitNotice
|
| 142 |
163 |
implements Serializable
|
| ... | ... | |
| 150 |
171 |
/** How long a sender waits for the other servers to apply its notice, in ms (sync mode). */
|
| 151 |
172 |
private static final long TIMEOUT_MS = 10000L;
|
| 152 |
173 |
|
| 153 |
|
/** The mode: sync (the default), batch, async or off. */
|
|
174 |
/** The mode: sync (the default), batch or async. */
|
| 154 |
175 |
private static final String MODE = "sync";
|
| 155 |
176 |
|
|
177 |
/** The delay (ms) of a call of {@link #resync}, so that the servers named meanwhile are told together. */
|
|
178 |
private static final long RESYNC_DELAY_MS = 1000L;
|
|
179 |
|
| 156 |
180 |
/** Batch mode: the least ms between two sends of a server. */
|
| 157 |
181 |
private static final long BATCH_MS = 5L;
|
| 158 |
182 |
|
| ... | ... | |
| 170 |
194 |
/** Batch mode: the sender thread, created on first use (guarded by {@link #batchPending}). */
|
| 171 |
195 |
private static ScheduledExecutorService flusher = null;
|
| 172 |
196 |
|
|
197 |
/** The thread of {@link #resync}, created on first use (guarded by {@link #batchPending}). */
|
|
198 |
private static ScheduledExecutorService resyncer = null;
|
|
199 |
|
|
200 |
/** The servers {@link #resync} tells in its next call. */
|
|
201 |
private static final Set<DistributedMember> resyncPending = new HashSet<>();
|
|
202 |
|
|
203 |
/** {@link #resync}'s next call tells every other server (guarded by {@link #resyncPending}). */
|
|
204 |
private static boolean resyncAll = false;
|
|
205 |
|
|
206 |
/** A call of {@link #resync} is scheduled and has not taken the pending servers yet (guarded likewise). */
|
|
207 |
private static boolean resyncScheduled = false;
|
|
208 |
|
| 173 |
209 |
/** The cluster key of the {@link DmoVersioning} of the database and tenant. */
|
| 174 |
210 |
private final String versioningKey;
|
| 175 |
211 |
|
| ... | ... | |
| 213 |
249 |
}
|
| 214 |
250 |
|
| 215 |
251 |
/**
|
| 216 |
|
* Check whether the servers send commit notices.
|
|
252 |
* This server may have missed commit notices of the other servers (see the class description): clear
|
|
253 |
* every result of its FastFindCaches and count a remote change of every record it tracks, so that every
|
|
254 |
* cached copy is read again before it is used.
|
| 217 |
255 |
*
|
| 218 |
|
* @return {@code true} unless the mode is {@code off}.
|
|
256 |
* @param why
|
|
257 |
* Why, for the log.
|
| 219 |
258 |
*/
|
| 220 |
|
public static boolean isEnabled()
|
|
259 |
public static void missed(String why)
|
| 221 |
260 |
{
|
| 222 |
|
return !"off".equals(MODE);
|
|
261 |
LOG.warning("Commit notices may have been missed (" + why + "): the fast-find caches are cleared and " +
|
|
262 |
"the cached records are read again");
|
|
263 |
FastFindCache.invalidateAllPermanent();
|
|
264 |
DmoVersioning.incrementRemoteAll();
|
| 223 |
265 |
}
|
| 224 |
266 |
|
| 225 |
267 |
/**
|
| ... | ... | |
| 274 |
316 |
* as the mode says: in sync mode (the default) in one call, and wait once (at most {@link #TIMEOUT_MS})
|
| 275 |
317 |
* until every other server has applied them; a server which does not answer in time or an error is
|
| 276 |
318 |
* logged and the commit goes on (Geode's wait is not interruptible; its interrupt case is defensive).
|
| 277 |
|
* Batch mode queues them, async mode sends them without waiting, off sends nothing.
|
|
319 |
* The servers which did not confirm the notices, or all other servers if the send failed, are told that
|
|
320 |
* they missed them ({@link #resync}). Batch mode queues them, async mode sends them without waiting (a
|
|
321 |
* failed send is told to all other servers too).
|
| 278 |
322 |
*
|
| 279 |
323 |
* @param notices
|
| 280 |
324 |
* The notices; not empty.
|
| 281 |
325 |
*/
|
| 282 |
326 |
private static void send(CommitNotice[] notices)
|
| 283 |
327 |
{
|
| 284 |
|
if ("off".equals(MODE))
|
| 285 |
|
{
|
| 286 |
|
return;
|
| 287 |
|
}
|
| 288 |
|
|
| 289 |
328 |
if ("batch".equals(MODE))
|
| 290 |
329 |
{
|
| 291 |
330 |
enqueue(notices);
|
| 292 |
331 |
return;
|
| 293 |
332 |
}
|
| 294 |
333 |
|
|
334 |
Set<DistributedMember> others = null;
|
|
335 |
Confirmations confirmed = new Confirmations();
|
| 295 |
336 |
try
|
| 296 |
337 |
{
|
| 297 |
338 |
// inside the try: on a server whose cache is closed (expelled) this fails, and the commit goes on
|
| 298 |
|
Set<DistributedMember> others = GeodeConfig.getOtherServerMembers();
|
|
339 |
others = GeodeConfig.getOtherServerMembers();
|
| 299 |
340 |
if (others.isEmpty())
|
| 300 |
341 |
{
|
| 301 |
342 |
return;
|
| 302 |
343 |
}
|
| 303 |
344 |
|
| 304 |
|
ResultCollector<?, ?> rc = FunctionService.onMembers(others)
|
| 305 |
|
.setArguments(notices)
|
| 306 |
|
.execute(Receiver.ID);
|
| 307 |
|
if ("sync".equals(MODE))
|
| 308 |
|
{
|
| 309 |
|
// one wait for all the other servers, which apply the notices in parallel
|
| 310 |
|
Object results = rc.getResult(TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
| 311 |
|
if (results instanceof List && ((List<?>) results).contains(-1))
|
| 312 |
|
{
|
| 313 |
|
LOG.warning("Commit notice of " + describe(notices) + " not applied by every server (see " +
|
| 314 |
|
"their log)");
|
| 315 |
|
}
|
|
345 |
ResultCollector<Object, Object> rc = FunctionService.onMembers(others)
|
|
346 |
.setArguments(notices)
|
|
347 |
.withCollector(confirmed)
|
|
348 |
.execute(Receiver.ID);
|
|
349 |
if (!"sync".equals(MODE))
|
|
350 |
{
|
|
351 |
return;
|
|
352 |
}
|
|
353 |
|
|
354 |
// one wait for all the other servers, which apply the notices in parallel
|
|
355 |
rc.getResult(TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
|
356 |
if (confirmed.failed())
|
|
357 |
{
|
|
358 |
LOG.warning("Commit notice of " + describe(notices) + " not applied by every server (see " +
|
|
359 |
"their log)");
|
| 316 |
360 |
}
|
| 317 |
361 |
}
|
| 318 |
362 |
catch (InterruptedException exc)
|
| ... | ... | |
| 326 |
370 |
LOG.warning("Commit notice of " + describe(notices) + " not confirmed by every server: " + exc);
|
| 327 |
371 |
LOG.log(Level.FINE, "Commit notice not confirmed", exc);
|
| 328 |
372 |
}
|
|
373 |
|
|
374 |
// the servers which did not answer may have missed the notices (one which answered -1 cleared itself);
|
|
375 |
// in async mode, only a failed send is known
|
|
376 |
Set<DistributedMember> missing = (others == null) ? null : confirmed.missing(others);
|
|
377 |
if (missing == null || !missing.isEmpty())
|
|
378 |
{
|
|
379 |
resync(missing, "commit notice of " + describe(notices) + " not confirmed");
|
|
380 |
}
|
|
381 |
}
|
|
382 |
|
|
383 |
/**
|
|
384 |
* Tell other servers that they may have missed commit notices: they clear what they cached
|
|
385 |
* ({@link #missed}). Sent on a daemon thread without waiting for an answer; the servers named while a call
|
|
386 |
* is pending are told in the next call, once each (so a series of timed-out notices makes a server clear
|
|
387 |
* once, not once per notice): a call starts {@link #RESYNC_DELAY_MS} ms after the first server was named,
|
|
388 |
* so that the notices timing out during one stall are told together. If this server is not a member of
|
|
389 |
* the cluster any more the call fails, which is logged (its departure makes the others clear).
|
|
390 |
*
|
|
391 |
* @param members
|
|
392 |
* The servers, or {@code null} for every other server.
|
|
393 |
* @param why
|
|
394 |
* Why, for their log.
|
|
395 |
*/
|
|
396 |
private static void resync(Set<DistributedMember> members, String why)
|
|
397 |
{
|
|
398 |
synchronized (resyncPending)
|
|
399 |
{
|
|
400 |
if (members == null)
|
|
401 |
{
|
|
402 |
resyncAll = true;
|
|
403 |
}
|
|
404 |
else
|
|
405 |
{
|
|
406 |
resyncPending.addAll(members);
|
|
407 |
}
|
|
408 |
|
|
409 |
if (resyncScheduled)
|
|
410 |
{
|
|
411 |
return;
|
|
412 |
}
|
|
413 |
resyncScheduled = true;
|
|
414 |
}
|
|
415 |
|
|
416 |
resyncer().schedule(() ->
|
|
417 |
{
|
|
418 |
Set<DistributedMember> to;
|
|
419 |
synchronized (resyncPending)
|
|
420 |
{
|
|
421 |
to = resyncAll ? GeodeConfig.getOtherServerMembers() : new HashSet<>(resyncPending);
|
|
422 |
resyncPending.clear();
|
|
423 |
resyncAll = false;
|
|
424 |
resyncScheduled = false;
|
|
425 |
}
|
|
426 |
|
|
427 |
try
|
|
428 |
{
|
|
429 |
if (!to.isEmpty())
|
|
430 |
{
|
|
431 |
FunctionService.onMembers(to).setArguments(why).execute(Missed.ID);
|
|
432 |
LOG.warning("Commit notices: told " + to.size() + " server(s) that they may have missed " +
|
|
433 |
"notices (" + why + ")");
|
|
434 |
}
|
|
435 |
}
|
|
436 |
catch (RuntimeException exc)
|
|
437 |
{
|
|
438 |
LOG.warning("Commit notices: could not tell the other servers that they may have missed " +
|
|
439 |
"notices (" + why + "): " + exc);
|
|
440 |
}
|
|
441 |
}, RESYNC_DELAY_MS, TimeUnit.MILLISECONDS);
|
| 329 |
442 |
}
|
| 330 |
443 |
|
| 331 |
444 |
/**
|
| ... | ... | |
| 392 |
505 |
catch (Exception exc)
|
| 393 |
506 |
{
|
| 394 |
507 |
LOG.log(Level.WARNING, "Commit notice batch not sent: " + exc, exc);
|
|
508 |
resync(null, "commit notice batch not sent");
|
| 395 |
509 |
}
|
| 396 |
510 |
}
|
| 397 |
511 |
|
| ... | ... | |
| 418 |
532 |
}
|
| 419 |
533 |
|
| 420 |
534 |
/**
|
|
535 |
* The daemon thread which tells other servers that they may have missed notices ({@link #resync}).
|
|
536 |
*
|
|
537 |
* @return The executor, created on first use.
|
|
538 |
*/
|
|
539 |
private static ScheduledExecutorService resyncer()
|
|
540 |
{
|
|
541 |
synchronized (batchPending)
|
|
542 |
{
|
|
543 |
if (resyncer == null)
|
|
544 |
{
|
|
545 |
resyncer = Executors.newSingleThreadScheduledExecutor(r ->
|
|
546 |
{
|
|
547 |
Thread t = new Thread(r, "FWD commit notice resync");
|
|
548 |
t.setDaemon(true);
|
|
549 |
return t;
|
|
550 |
});
|
|
551 |
}
|
|
552 |
return resyncer;
|
|
553 |
}
|
|
554 |
}
|
|
555 |
|
|
556 |
/**
|
| 421 |
557 |
* Apply the notice to the record versions of this server.
|
| 422 |
558 |
*
|
| 423 |
559 |
* @return The number of records whose version this server tracked.
|
| ... | ... | |
| 460 |
596 |
dmoIds.add(FastFindCache.combine(dmoMeta.getId(), null));
|
| 461 |
597 |
}
|
| 462 |
598 |
}
|
| 463 |
|
// a remote request: invalidate here only, never broadcast again
|
| 464 |
|
FastFindCache.setIsFromRemoteRequest();
|
| 465 |
599 |
ffCache.invalidate(dmoIds);
|
| 466 |
600 |
}
|
| 467 |
601 |
|
| ... | ... | |
| 541 |
675 |
* Apply the notices (one per database and tenant) and answer the number of tracked records. The record
|
| 542 |
676 |
* versions of every notice are applied first, then the FastFindCache invalidations, each notice on its
|
| 543 |
677 |
* own, so a failure leaves the others applied. Always answers: an exception sent back to the sender
|
| 544 |
|
* would end its wait for every server, not only this one; a failure is logged and answered with -1.
|
|
678 |
* would end its wait for every server, not only this one; a failure is logged, this server clears what
|
|
679 |
* it cached ({@link CommitNotice#missed}), and it answers -1.
|
| 545 |
680 |
*
|
| 546 |
681 |
* @param context
|
| 547 |
682 |
* The function context, whose argument is the notices.
|
| ... | ... | |
| 576 |
711 |
notApplied(notice, exc);
|
| 577 |
712 |
}
|
| 578 |
713 |
}
|
|
714 |
if (failed)
|
|
715 |
{
|
|
716 |
try
|
|
717 |
{
|
|
718 |
missed("a commit notice could not be applied on this server");
|
|
719 |
}
|
|
720 |
catch (RuntimeException | LinkageError | AssertionError exc)
|
|
721 |
{
|
|
722 |
// always answer (see above)
|
|
723 |
LOG.warning("Fast-find caches not cleared after a failed commit notice: " + exc);
|
|
724 |
}
|
|
725 |
}
|
| 579 |
726 |
context.getResultSender().lastResult(failed ? -1 : tracked);
|
| 580 |
727 |
}
|
| 581 |
728 |
|
| ... | ... | |
| 639 |
786 |
LOG.log(Level.FINE, "Commit notice not applied", exc);
|
| 640 |
787 |
}
|
| 641 |
788 |
}
|
|
789 |
|
|
790 |
/**
|
|
791 |
* The collector of the answers to a notice in sync mode (Geode waits for them, see
|
|
792 |
* {@link ResultCollector#getResult(long, TimeUnit)}): it records which servers answered, so that the
|
|
793 |
* others can be told that they may have missed the notice.
|
|
794 |
*/
|
|
795 |
private static final class Confirmations
|
|
796 |
implements ResultCollector<Object, Object>
|
|
797 |
{
|
|
798 |
/** {@code System.nanoTime()} when the collector was made (right before the notice was sent). */
|
|
799 |
private final long sentNanos = System.nanoTime();
|
|
800 |
|
|
801 |
/** {@code System.currentTimeMillis()} likewise. */
|
|
802 |
private final long sentWall = System.currentTimeMillis();
|
|
803 |
|
|
804 |
/** The cluster gate's epoch likewise. */
|
|
805 |
private final long epoch = ClusterGate.epoch();
|
|
806 |
|
|
807 |
/** The servers which answered. */
|
|
808 |
private final Set<DistributedMember> answered = ConcurrentHashMap.newKeySet();
|
|
809 |
|
|
810 |
/** The answers. */
|
|
811 |
private final List<Object> results = Collections.synchronizedList(new ArrayList<>());
|
|
812 |
|
|
813 |
/** {@code true} if a server answered that it could not apply the notice (-1). */
|
|
814 |
private volatile boolean failed = false;
|
|
815 |
|
|
816 |
/**
|
|
817 |
* A server answered (an exception, e.g. for a server which left, is no confirmation).
|
|
818 |
*
|
|
819 |
* @param member
|
|
820 |
* The server.
|
|
821 |
* @param result
|
|
822 |
* Its answer.
|
|
823 |
*/
|
|
824 |
@Override
|
|
825 |
public void addResult(DistributedMember member, Object result)
|
|
826 |
{
|
|
827 |
results.add(result);
|
|
828 |
if (result instanceof Throwable)
|
|
829 |
{
|
|
830 |
return;
|
|
831 |
}
|
|
832 |
|
|
833 |
if (Integer.valueOf(-1).equals(result))
|
|
834 |
{
|
|
835 |
failed = true;
|
|
836 |
}
|
|
837 |
answered.add(member);
|
|
838 |
|
|
839 |
// an answer also shows that the server still counts this one a member (its membership lease)
|
|
840 |
MembershipLease.answered(member, sentNanos, sentWall, epoch);
|
|
841 |
}
|
|
842 |
|
|
843 |
/**
|
|
844 |
* The answers so far.
|
|
845 |
*
|
|
846 |
* @return The answers.
|
|
847 |
*/
|
|
848 |
@Override
|
|
849 |
public Object getResult()
|
|
850 |
{
|
|
851 |
return results;
|
|
852 |
}
|
|
853 |
|
|
854 |
/**
|
|
855 |
* The answers so far (Geode's execution waits for them before it calls this).
|
|
856 |
*
|
|
857 |
* @param timeout
|
|
858 |
* Ignored.
|
|
859 |
* @param unit
|
|
860 |
* Ignored.
|
|
861 |
*
|
|
862 |
* @return The answers.
|
|
863 |
*/
|
|
864 |
@Override
|
|
865 |
public Object getResult(long timeout, TimeUnit unit)
|
|
866 |
{
|
|
867 |
return results;
|
|
868 |
}
|
|
869 |
|
|
870 |
/**
|
|
871 |
* Every server answered: nothing to do.
|
|
872 |
*/
|
|
873 |
@Override
|
|
874 |
public void endResults()
|
|
875 |
{
|
|
876 |
}
|
|
877 |
|
|
878 |
/**
|
|
879 |
* Forget the answers (Geode calls this before it executes again; notices are not re-executed).
|
|
880 |
*/
|
|
881 |
@Override
|
|
882 |
public void clearResults()
|
|
883 |
{
|
|
884 |
results.clear();
|
|
885 |
answered.clear();
|
|
886 |
failed = false;
|
|
887 |
}
|
|
888 |
|
|
889 |
/**
|
|
890 |
* Whether a server answered that it could not apply the notice.
|
|
891 |
*
|
|
892 |
* @return {@code true} if one did (it cleared what it cached).
|
|
893 |
*/
|
|
894 |
boolean failed()
|
|
895 |
{
|
|
896 |
return failed;
|
|
897 |
}
|
|
898 |
|
|
899 |
/**
|
|
900 |
* The servers which did not answer.
|
|
901 |
*
|
|
902 |
* @param sentTo
|
|
903 |
* The servers the notice was sent to.
|
|
904 |
*
|
|
905 |
* @return Those of them which did not answer (a new set).
|
|
906 |
*/
|
|
907 |
Set<DistributedMember> missing(Set<DistributedMember> sentTo)
|
|
908 |
{
|
|
909 |
Set<DistributedMember> missing = new HashSet<>(sentTo);
|
|
910 |
missing.removeAll(answered);
|
|
911 |
|
|
912 |
return missing;
|
|
913 |
}
|
|
914 |
}
|
|
915 |
|
|
916 |
/**
|
|
917 |
* The Geode function which tells a server that it may have missed commit notices
|
|
918 |
* ({@link CommitNotice#resync}): it clears what it cached ({@link CommitNotice#missed}).
|
|
919 |
*/
|
|
920 |
public static class Missed
|
|
921 |
implements Function<String>
|
|
922 |
{
|
|
923 |
/** The function id. */
|
|
924 |
static final String ID = Missed.class.getName();
|
|
925 |
|
|
926 |
/** Serial version. */
|
|
927 |
private static final long serialVersionUID = 1L;
|
|
928 |
|
|
929 |
/**
|
|
930 |
* Clear what this server cached.
|
|
931 |
*
|
|
932 |
* @param context
|
|
933 |
* The function context, whose argument tells why.
|
|
934 |
*/
|
|
935 |
@Override
|
|
936 |
public void execute(FunctionContext<String> context)
|
|
937 |
{
|
|
938 |
missed("told by another server: " + context.getArguments());
|
|
939 |
}
|
|
940 |
|
|
941 |
/**
|
|
942 |
* The function id.
|
|
943 |
*
|
|
944 |
* @return {@link #ID}.
|
|
945 |
*/
|
|
946 |
@Override
|
|
947 |
public String getId()
|
|
948 |
{
|
|
949 |
return ID;
|
|
950 |
}
|
|
951 |
|
|
952 |
/**
|
|
953 |
* The sender does not wait for an answer.
|
|
954 |
*
|
|
955 |
* @return {@code false}.
|
|
956 |
*/
|
|
957 |
@Override
|
|
958 |
public boolean hasResult()
|
|
959 |
{
|
|
960 |
return false;
|
|
961 |
}
|
|
962 |
|
|
963 |
/**
|
|
964 |
* Not sent again to another member when a member fails.
|
|
965 |
*
|
|
966 |
* @return {@code false}.
|
|
967 |
*/
|
|
968 |
@Override
|
|
969 |
public boolean isHA()
|
|
970 |
{
|
|
971 |
return false;
|
|
972 |
}
|
|
973 |
|
|
974 |
/**
|
|
975 |
* Not a data-dependent function.
|
|
976 |
*
|
|
977 |
* @return {@code false}.
|
|
978 |
*/
|
|
979 |
@Override
|
|
980 |
public boolean optimizeForWrite()
|
|
981 |
{
|
|
982 |
return false;
|
|
983 |
}
|
|
984 |
}
|
| 642 |
985 |
}
|
| 643 |
|
-- a/src/com/goldencode/p2j/distributed/config/ClusterGate.java
|
|
986 |
++ b/src/com/goldencode/p2j/distributed/config/ClusterGate.java
|
| ... | ... | |
| 10 |
10 |
** every session is terminated, logins and appserver requests are refused until the
|
| 11 |
11 |
** in-process re-initialisation after Geode's reconnect, through participants, is done;
|
| 12 |
12 |
** the appserver agents are started again after it).
|
|
13 |
** CA 20261029 Commit fence (enterCommit()/exitCommit()): closing() refuses new commits and waits for
|
|
14 |
** those in flight before the cache closes; started() starts the MembershipLease pinger.
|
| 13 |
15 |
*/
|
| 14 |
16 |
|
| 15 |
17 |
/*
|
| ... | ... | |
| 191 |
193 |
*/
|
| 192 |
194 |
private static final long MSA_UNWIND_MS = 30_000L;
|
| 193 |
195 |
|
|
196 |
/** The longest wait (ms) of {@link #closing} for the commits in flight ({@link #enterCommit}). */
|
|
197 |
private static final long FENCE_WAIT_MS = 10_000L;
|
|
198 |
|
|
199 |
/** Guards {@link #commitsInFlight} and {@link #fenceClosed}; {@link #closing} waits on it. */
|
|
200 |
private static final Object FENCE = new Object();
|
|
201 |
|
| 194 |
202 |
/** Session ids above this one are local ({@link #localSessionId}); the cluster's sequence stays below. */
|
| 195 |
203 |
private static final int LOCAL_SESSION_ID_FLOOR = 2_000_000_000;
|
| 196 |
204 |
|
| ... | ... | |
| 252 |
260 |
*/
|
| 253 |
261 |
private static volatile boolean rejoinFailed = false;
|
| 254 |
262 |
|
|
263 |
/** The number of commits between {@link #enterCommit} and {@link #exitCommit} (guarded by FENCE). */
|
|
264 |
private static int commitsInFlight = 0;
|
|
265 |
|
|
266 |
/** {@code true} once {@link #closing} started: no commit enters any more (guarded by FENCE). */
|
|
267 |
private static boolean fenceClosed = false;
|
|
268 |
|
| 255 |
269 |
/** The re-initialisation participants, kept sorted by their order. */
|
| 256 |
270 |
private static final List<Registered> PARTICIPANTS = new CopyOnWriteArrayList<>();
|
| 257 |
271 |
|
| ... | ... | |
| 604 |
618 |
}
|
| 605 |
619 |
|
| 606 |
620 |
/**
|
|
621 |
* Enter the commit fence before the commit of a database transaction ({@code TxWrapper}), before every
|
|
622 |
* other check of the commit; {@link #exitCommit} must follow once the database commit is done or failed.
|
|
623 |
* While a commit is inside the fence, FWD's shutdown does not close the cache ({@link #closing} waits for
|
|
624 |
* it), so its locks are still held by this member when its change reaches the database; once the shutdown
|
|
625 |
* started, no commit enters.
|
|
626 |
*
|
|
627 |
* @param db
|
|
628 |
* The database whose transaction commits.
|
|
629 |
*
|
|
630 |
* @throws DatabaseConnectionException
|
|
631 |
* if FWD's shutdown closes the cache (the database transaction is rolled back).
|
|
632 |
*/
|
|
633 |
public static void enterCommit(Database db)
|
|
634 |
{
|
|
635 |
synchronized (FENCE)
|
|
636 |
{
|
|
637 |
if (!fenceClosed)
|
|
638 |
{
|
|
639 |
commitsInFlight++;
|
|
640 |
return;
|
|
641 |
}
|
|
642 |
}
|
|
643 |
|
|
644 |
throw (DatabaseConnectionException) unavailable(db, null);
|
|
645 |
}
|
|
646 |
|
|
647 |
/**
|
|
648 |
* Leave the commit fence entered by {@link #enterCommit}.
|
|
649 |
*/
|
|
650 |
public static void exitCommit()
|
|
651 |
{
|
|
652 |
synchronized (FENCE)
|
|
653 |
{
|
|
654 |
commitsInFlight--;
|
|
655 |
if (commitsInFlight <= 0)
|
|
656 |
{
|
|
657 |
FENCE.notifyAll();
|
|
658 |
}
|
|
659 |
}
|
|
660 |
}
|
|
661 |
|
|
662 |
/**
|
| 607 |
663 |
* Build the STOP of the gate.
|
| 608 |
664 |
*
|
| 609 |
665 |
* @param db
|
| ... | ... | |
| 723 |
779 |
t.start();
|
| 724 |
780 |
|
| 725 |
781 |
LOG.log(Level.INFO, "Cluster gate: running (watchdog every " + WATCH_MS + " ms)");
|
|
782 |
|
|
783 |
MembershipLease.start(c);
|
| 726 |
784 |
}
|
| 727 |
785 |
|
| 728 |
786 |
/**
|
| 729 |
787 |
* FWD closes the cache (server shutdown): the gate is {@link State#CLOSED} from now on, so code needing
|
| 730 |
788 |
* the cluster gets STOP; the epoch is kept until the cache is closed ({@link #closed}), so that the
|
| 731 |
|
* releases of the sessions still reach the owners meanwhile. Nobody is interrupted. Called by
|
| 732 |
|
* {@link GeodeConfig}'s shutdown hook before it closes the cache.
|
|
789 |
* releases of the sessions still reach the owners meanwhile. Nobody is interrupted. No commit enters the
|
|
790 |
* commit fence any more, and the commits in it are waited for (at most {@link #FENCE_WAIT_MS} ms), so that
|
|
791 |
* none reaches its database after the cache closed and the other members purged this member's locks.
|
|
792 |
* Called by {@link GeodeConfig}'s shutdown hook before it closes the cache.
|
| 733 |
793 |
*/
|
| 734 |
794 |
static void closing()
|
| 735 |
795 |
{
|
| ... | ... | |
| 754 |
814 |
}
|
| 755 |
815 |
|
| 756 |
816 |
LOG.log(Level.INFO, "Cluster gate: closed (the server shuts down)");
|
|
817 |
|
|
818 |
awaitCommits();
|
| 757 |
819 |
}
|
| 758 |
820 |
|
| 759 |
821 |
/**
|
| ... | ... | |
| 810 |
872 |
}
|
| 811 |
873 |
|
| 812 |
874 |
/**
|
|
875 |
* Close the commit fence and wait (at most {@link #FENCE_WAIT_MS} ms) until no commit is in it; an
|
|
876 |
* interrupt ends the wait and is kept.
|
|
877 |
*/
|
|
878 |
private static void awaitCommits()
|
|
879 |
{
|
|
880 |
boolean interrupted = false;
|
|
881 |
int left;
|
|
882 |
synchronized (FENCE)
|
|
883 |
{
|
|
884 |
fenceClosed = true;
|
|
885 |
long end = System.nanoTime() + FENCE_WAIT_MS * 1_000_000L;
|
|
886 |
long ms;
|
|
887 |
while (commitsInFlight > 0 && !interrupted && (ms = (end - System.nanoTime()) / 1_000_000L) > 0)
|
|
888 |
{
|
|
889 |
try
|
|
890 |
{
|
|
891 |
FENCE.wait(ms);
|
|
892 |
}
|
|
893 |
catch (InterruptedException exc)
|
|
894 |
{
|
|
895 |
interrupted = true;
|
|
896 |
}
|
|
897 |
}
|
|
898 |
left = commitsInFlight;
|
|
899 |
}
|
|
900 |
|
|
901 |
if (interrupted)
|
|
902 |
{
|
|
903 |
Thread.currentThread().interrupt();
|
|
904 |
}
|
|
905 |
|
|
906 |
if (left > 0)
|
|
907 |
{
|
|
908 |
LOG.log(Level.WARNING, "Cluster gate: " + left + " commit(s) still in flight when the cache closes");
|
|
909 |
}
|
|
910 |
}
|
|
911 |
|
|
912 |
/**
|
| 813 |
913 |
* Whether a cache is closed or closing.
|
| 814 |
914 |
*
|
| 815 |
915 |
* @param c
|
| 816 |
|
-- a/src/com/goldencode/p2j/distributed/config/GeodeConfig.java
|
|
916 |
++ b/src/com/goldencode/p2j/distributed/config/GeodeConfig.java
|
| ... | ... | |
| 28 |
28 |
** re-initialisation participants: the reconnected cache is adopted and FWD's functions
|
| 29 |
29 |
** registered again (registerFunctions()), ManagerClasses re-checked, the rebalance
|
| 30 |
30 |
** coordinator started on it, the fast-find caches cleared and the record versions bumped.
|
|
31 |
** CA 20261029 Registered MembershipLease.Ping/Check and CommitNotice.Missed; removed the registration of
|
|
32 |
** FastFindCacheInvalidationFunction; missedNotices() goes through CommitNotice.missed();
|
|
33 |
** added isServerMember().
|
| 31 |
34 |
*/
|
| 32 |
35 |
|
| 33 |
36 |
/*
|
| ... | ... | |
| 279 |
282 |
private static void registerFunctions()
|
| 280 |
283 |
{
|
| 281 |
284 |
FunctionService.registerFunction(new GetP2JJarVersionFunction());
|
| 282 |
|
FunctionService.registerFunction(new FastFindCacheInvalidationFunction());
|
| 283 |
285 |
FunctionService.registerFunction(new com.goldencode.p2j.distributed.cache.CommitNotice.Receiver());
|
|
286 |
FunctionService.registerFunction(new com.goldencode.p2j.distributed.cache.CommitNotice.Missed());
|
|
287 |
FunctionService.registerFunction(new MembershipLease.Ping());
|
|
288 |
FunctionService.registerFunction(new MembershipLease.Check());
|
| 284 |
289 |
FunctionService.registerFunction(new TenantRegistryReloadFunction());
|
| 285 |
290 |
FunctionService.registerFunction(new com.goldencode.p2j.distributed.lock.OwnerLockFunction());
|
| 286 |
291 |
FunctionService.registerFunction(new com.goldencode.p2j.distributed.lock.TableLeases.RevokeFunction());
|
| ... | ... | |
| 334 |
339 |
*/
|
| 335 |
340 |
private static void missedNotices()
|
| 336 |
341 |
{
|
| 337 |
|
FastFindCache.invalidateAllPermanent();
|
| 338 |
|
DmoVersioning.incrementRemoteAll();
|
|
342 |
com.goldencode.p2j.distributed.cache.CommitNotice.missed("this server was not a member of the cluster");
|
| 339 |
343 |
}
|
| 340 |
344 |
|
| 341 |
345 |
/**
|
| ... | ... | |
| 546 |
550 |
}
|
| 547 |
551 |
|
| 548 |
552 |
/**
|
|
553 |
* Whether a member of the distributed system is an FWD server (not, e.g., a locator).
|
|
554 |
*
|
|
555 |
* @param member
|
|
556 |
* The member.
|
|
557 |
*
|
|
558 |
* @return {@code true} if it belongs to the FWD server group.
|
|
559 |
*/
|
|
560 |
public static boolean isServerMember(DistributedMember member)
|
|
561 |
{
|
|
562 |
return member != null && member.getGroups().contains(FWD_SERVER_GROUP);
|
|
563 |
}
|
|
564 |
|
|
565 |
/**
|
| 549 |
566 |
* Constructs the locator string in the format "host[port]".
|
| 550 |
567 |
*
|
| 551 |
568 |
* @return A string formatted for Geode's "locators" property.
|
| 552 |
|
-- a/src/com/goldencode/p2j/distributed/lock/LiveMembers.java
|
|
569 |
++ b/src/com/goldencode/p2j/distributed/lock/LiveMembers.java
|
| ... | ... | |
| 8 |
8 |
** 001 ES 20261026 Initial version (#4369: liveness checks without a member set built per operation).
|
| 9 |
9 |
** CA 20261028 The view is built from GeodeConfig's cache, not CacheFactory.getAnyInstance(), which waits
|
| 10 |
10 |
** for Geode's reconnect and then answers the reconnected cache.
|
|
11 |
** CA 20261029 holdsLocks()/lockerChecker(): a departed member's locks and reservations still stand for
|
|
12 |
** MembershipLease.GRACE_MS after this member learned it is gone (GONE).
|
| 11 |
13 |
*/
|
| 12 |
14 |
|
| 13 |
15 |
/*
|
| ... | ... | |
| 91 |
93 |
* not list it: a member missing from the cached view is checked against the current view before the
|
| 92 |
94 |
* answer, so a live member is never purged or refused because of a stale cache.</li>
|
| 93 |
95 |
* </ul>
|
|
96 |
* The locks and unique key reservations of a member which is gone still stand for
|
|
97 |
* {@link MembershipLease#GRACE_MS} ms after this member learned of it ({@link #holdsLocks},
|
|
98 |
* {@link #lockerChecker}): a member paused past the member timeout may still be committing under them until
|
|
99 |
* its membership lease expired. A departed member is never granted anything new ({@link #isLive},
|
|
100 |
* {@link #checker}, {@link #hasLeft} have no grace).
|
| 94 |
101 |
*/
|
| 95 |
102 |
public final class LiveMembers
|
| 96 |
103 |
{
|
| ... | ... | |
| 100 |
107 |
/** Time (ms) a member whose departure was reported is remembered. */
|
| 101 |
108 |
private static final long DEPARTED_KEEP_MS = 3_600_000L;
|
| 102 |
109 |
|
|
110 |
/** Size of {@link #GONE} above which its old entries are dropped at the next grace check. */
|
|
111 |
private static final int GONE_PRUNE_SIZE = 256;
|
|
112 |
|
| 103 |
113 |
/** The view answering while the cache is closing: every member live, so nothing is purged. */
|
| 104 |
114 |
private static final View CLOSING = new View(null, null, Collections.emptySet(), 0L);
|
| 105 |
115 |
|
| 106 |
116 |
/** Members whose departure the membership listener reported, with the time of the report (ms). */
|
| 107 |
117 |
private static final ConcurrentHashMap<String, Long> DEPARTED = new ConcurrentHashMap<>();
|
| 108 |
118 |
|
|
119 |
/**
|
|
120 |
* Members known to be gone (departure reported, or missing from a current view), with the time this
|
|
121 |
* member learned it ({@code System.nanoTime()}); for the grace of their locks ({@link #holdsLocks}).
|
|
122 |
*/
|
|
123 |
private static final ConcurrentHashMap<String, Long> GONE = new ConcurrentHashMap<>();
|
|
124 |
|
| 109 |
125 |
/** The cached view; {@code null} to build a new one at the next check. */
|
| 110 |
126 |
private static volatile View view = null;
|
| 111 |
127 |
|
| ... | ... | |
| 154 |
170 |
}
|
| 155 |
171 |
|
| 156 |
172 |
/**
|
|
173 |
* Whether the locks and unique key reservations a member holds still stand: it is live ({@link #isLive}),
|
|
174 |
* or it is gone since less than {@link MembershipLease#GRACE_MS} ms (see the class description).
|
|
175 |
*
|
|
176 |
* @param member
|
|
177 |
* Member id.
|
|
178 |
*
|
|
179 |
* @return {@code true} if they do.
|
|
180 |
*/
|
|
181 |
public static boolean holdsLocks(String member)
|
|
182 |
{
|
|
183 |
return isLive(member) || inGrace(member);
|
|
184 |
}
|
|
185 |
|
|
186 |
/**
|
|
187 |
* A check for several lockers in one decision, as {@link #checker} (one view at most) but with the grace
|
|
188 |
* of {@link #holdsLocks}: whether a locker's locks still stand.
|
|
189 |
*
|
|
190 |
* @return The check; for one decision only (not thread safe).
|
|
191 |
*/
|
|
192 |
public static Predicate<String> lockerChecker()
|
|
193 |
{
|
|
194 |
Predicate<String> live = checker();
|
|
195 |
|
|
196 |
return member -> live.test(member) || inGrace(member);
|
|
197 |
}
|
|
198 |
|
|
199 |
/**
|
| 157 |
200 |
* Whether the membership listener reported the departure of a member (a definite answer: member ids are
|
| 158 |
201 |
* not reused).
|
| 159 |
202 |
*
|
| ... | ... | |
| 193 |
236 |
long now = System.currentTimeMillis();
|
| 194 |
237 |
DEPARTED.put(member, now);
|
| 195 |
238 |
DEPARTED.values().removeIf(t -> now - t > DEPARTED_KEEP_MS);
|
|
239 |
long nanos = System.nanoTime();
|
|
240 |
GONE.put(member, nanos);
|
|
241 |
GONE.values().removeIf(t -> nanos - t > DEPARTED_KEEP_MS * 1_000_000L);
|
| 196 |
242 |
view = null;
|
| 197 |
243 |
}
|
| 198 |
244 |
|
| ... | ... | |
| 205 |
251 |
}
|
| 206 |
252 |
|
| 207 |
253 |
/**
|
|
254 |
* Whether a member which is not live is gone since less than {@link MembershipLease#GRACE_MS} ms (from
|
|
255 |
* the time this member learned it; the first time is noted here if the departure was not reported).
|
|
256 |
*
|
|
257 |
* @param member
|
|
258 |
* Member id, of a member which is not live.
|
|
259 |
*
|
|
260 |
* @return {@code true} if it is.
|
|
261 |
*/
|
|
262 |
private static boolean inGrace(String member)
|
|
263 |
{
|
|
264 |
long now = System.nanoTime();
|
|
265 |
long since = GONE.computeIfAbsent(member, m -> now);
|
|
266 |
if (GONE.size() > GONE_PRUNE_SIZE)
|
|
267 |
{
|
|
268 |
GONE.values().removeIf(t -> now - t > DEPARTED_KEEP_MS * 1_000_000L);
|
|
269 |
}
|
|
270 |
|
|
271 |
return now - since < MembershipLease.GRACE_MS * 1_000_000L;
|
|
272 |
}
|
|
273 |
|
|
274 |
/**
|
| 208 |
275 |
* The cached view, or a new one if there is none, it is older than {@link #TTL_MS}, or its distributed
|
| 209 |
276 |
* system is not connected any more (e.g. reconnected with a new one).
|
| 210 |
277 |
*
|
| 211 |
|
-- a/src/com/goldencode/p2j/distributed/lock/LockMembershipHandler.java
|
|
278 |
++ b/src/com/goldencode/p2j/distributed/lock/LockMembershipHandler.java
|
| ... | ... | |
| 9 |
9 |
** ES 20261026 No purge while this member's cache is closing; a failed purge never reaches the uncaught
|
| 10 |
10 |
** exception handler; membership events update LiveMembers.
|
| 11 |
11 |
** CA 20261029 Added reregister(): the listener is registered again on Geode's reconnected cache.
|
|
12 |
** CA 20261029 When an FWD server departs, its commit notices may be lost: once its membership lease
|
|
13 |
** expired, this server clears what it cached (CommitNotice.missed()).
|
| 12 |
14 |
*/
|
| 13 |
15 |
|
| 14 |
16 |
/*
|
| ... | ... | |
| 153 |
155 |
@Override
|
| 154 |
156 |
public void memberLeft(MembershipEvent event)
|
| 155 |
157 |
{
|
| 156 |
|
departed(event.getMemberId(), "left");
|
|
158 |
departed(event, "left");
|
| 157 |
159 |
}
|
| 158 |
160 |
|
| 159 |
161 |
/**
|
| ... | ... | |
| 165 |
167 |
@Override
|
| 166 |
168 |
public void memberCrashed(MembershipEvent event)
|
| 167 |
169 |
{
|
| 168 |
|
departed(event.getMemberId(), "crashed");
|
|
170 |
departed(event, "crashed");
|
| 169 |
171 |
}
|
| 170 |
172 |
|
| 171 |
173 |
/**
|
| 172 |
|
* Purge the locks and unique key reservations of a departed member, on separate threads (each makes a
|
| 173 |
|
* second pass later).
|
|
174 |
* Purge the locks and unique key reservations of a departed member, on separate threads (each waits for
|
|
175 |
* the grace of the member's membership lease first, and makes a second pass later). When the member is an
|
|
176 |
* FWD server, its commit notices of the last moments may be lost (it committed and did not send them, or
|
|
177 |
* was sending them): once it can no longer commit (the same grace), this server clears what it cached
|
|
178 |
* ({@code CommitNotice.missed}).
|
| 174 |
179 |
*
|
| 175 |
|
* @param memberId
|
| 176 |
|
* Id of the departed member.
|
|
180 |
* @param event
|
|
181 |
* The membership event, naming the departed member.
|
| 177 |
182 |
* @param how
|
| 178 |
183 |
* Description of the departure.
|
| 179 |
184 |
*/
|
| 180 |
|
private void departed(String memberId, String how)
|
|
185 |
private void departed(MembershipEvent event, String how)
|
| 181 |
186 |
{
|
|
187 |
String memberId = event.getMemberId();
|
| 182 |
188 |
LiveMembers.departed(memberId);
|
| 183 |
189 |
|
| 184 |
190 |
if (OwnerLockManager.cacheClosing())
|
| ... | ... | |
| 193 |
199 |
start(() -> com.goldencode.p2j.distributed.orm.OwnerUniqueIndex.purgeDepartedMember(memberId),
|
| 194 |
200 |
"Owner-Unique-Purge",
|
| 195 |
201 |
memberId);
|
|
202 |
if (GeodeConfig.isServerMember(event.getDistributedMember()))
|
|
203 |
{
|
|
204 |
start(() -> missedNotices(memberId, how), "Commit-Notice-Missed", memberId);
|
|
205 |
}
|
|
206 |
}
|
|
207 |
|
|
208 |
/**
|
|
209 |
* Clear what this server cached once the grace of a departed server's membership lease ended (see
|
|
210 |
* {@link #departed}); nothing if this member's cache is closing meanwhile (its own re-initialisation
|
|
211 |
* clears).
|
|
212 |
*
|
|
213 |
* @param memberId
|
|
214 |
* Id of the departed server.
|
|
215 |
* @param how
|
|
216 |
* Description of the departure.
|
|
217 |
*/
|
|
218 |
private static void missedNotices(String memberId, String how)
|
|
219 |
{
|
|
220 |
try
|
|
221 |
{
|
|
222 |
Thread.sleep(MembershipLease.GRACE_MS);
|
|
223 |
}
|
|
224 |
catch (InterruptedException exc)
|
|
225 |
{
|
|
226 |
return;
|
|
227 |
}
|
|
228 |
|
|
229 |
if (!OwnerLockManager.cacheClosing())
|
|
230 |
{
|
|
231 |
com.goldencode.p2j.distributed.cache.CommitNotice.missed("server " + memberId + " " + how);
|
|
232 |
}
|
| 196 |
233 |
}
|
| 197 |
234 |
|
| 198 |
235 |
/**
|
| 199 |
|
-- a/src/com/goldencode/p2j/distributed/lock/OwnerLockFunction.java
|
|
236 |
++ b/src/com/goldencode/p2j/distributed/lock/OwnerLockFunction.java
|
| ... | ... | |
| 34 |
34 |
** request or an older one (a late give-up never takes a newer lock); a CANCEL makes the
|
| 35 |
35 |
** older requests stale (LockQueue.giveUp); a call of a request older than the session's
|
| 36 |
36 |
** lock is not applied (olderThanLock()); give-ups with an id skip the call-number test.
|
|
37 |
** CA 20261029 The locks of a departed member are removed only after the grace of its membership lease
|
|
38 |
** (LiveMembers.lockerChecker() in purgeHere() and decide()).
|
| 37 |
39 |
*/
|
| 38 |
40 |
|
| 39 |
41 |
/*
|
| ... | ... | |
| 1127 |
1129 |
*/
|
| 1128 |
1130 |
private static boolean purgeHere(Region<Object, OwnerLockEntry> region, Object key)
|
| 1129 |
1131 |
{
|
| 1130 |
|
Predicate<String> live = LiveMembers.checker();
|
|
1132 |
// a departed member's locks stand until its membership lease expired (it may still be committing)
|
|
1133 |
Predicate<String> live = LiveMembers.lockerChecker();
|
| 1131 |
1134 |
boolean changed = false;
|
| 1132 |
1135 |
List<SessionToken> front = Collections.emptyList();
|
| 1133 |
1136 |
Object qk = new QueueKey(region.getName(), key);
|
| ... | ... | |
| 1219 |
1222 |
}
|
| 1220 |
1223 |
|
| 1221 |
1224 |
LockType requested = LockType.fromInt(req.type).toWaitVariant();
|
| 1222 |
|
Predicate<String> live = null;
|
| 1223 |
1225 |
OwnerLockEntry base = before;
|
| 1224 |
1226 |
boolean departed = false;
|
| 1225 |
1227 |
|
| 1226 |
1228 |
LockPolicy.Outcome outcome = decide(base, token, requested);
|
| 1227 |
1229 |
if (outcome == LockPolicy.Outcome.WAIT)
|
| 1228 |
1230 |
{
|
| 1229 |
|
// remove locks left by departed members so they never block anyone
|
| 1230 |
|
live = LiveMembers.checker();
|
| 1231 |
|
if (base.hasLockerOutside(live))
|
|
1231 |
// remove locks left by departed members so they never block anyone, once their membership lease
|
|
1232 |
// expired (until then they may still be committing under them)
|
|
1233 |
Predicate<String> lockers = LiveMembers.lockerChecker();
|
|
1234 |
if (base.hasLockerOutside(lockers))
|
| 1232 |
1235 |
{
|
| 1233 |
|
base = base.purgeMembers(live);
|
|
1236 |
base = base.purgeMembers(lockers);
|
| 1234 |
1237 |
outcome = decide(base, token, requested);
|
| 1235 |
1238 |
LOG.log(Level.WARNING, "[" + req.database + "] lock on " + req.key +
|
| 1236 |
1239 |
" was held by a departed member; removed");
|
| ... | ... | |
| 1241 |
1244 |
if (outcome == LockPolicy.Outcome.GRANT && req.update)
|
| 1242 |
1245 |
{
|
| 1243 |
1246 |
// a request arriving after its member left must not leave a lock behind
|
| 1244 |
|
boolean requesterLive = LiveMembers.isLocal(req.member) ||
|
| 1245 |
|
(live != null ? live : LiveMembers.checker()).test(req.member);
|
|
1247 |
boolean requesterLive = LiveMembers.isLocal(req.member) || LiveMembers.isLive(req.member);
|
| 1246 |
1248 |
if (requesterLive)
|
| 1247 |
1249 |
{
|
| 1248 |
1250 |
after = base.grant(token, req.member, requested, req.req);
|
| 1249 |
|
-- a/src/com/goldencode/p2j/distributed/lock/OwnerLockManager.java
|
|
1251 |
++ b/src/com/goldencode/p2j/distributed/lock/OwnerLockManager.java
|
| ... | ... | |
| 67 |
67 |
** holding lost locks; lost locks are released locally; added holdsStaleLocks(); the
|
| 68 |
68 |
** re-initialisation participant rebinds the lock regions of the databases still in use
|
| 69 |
69 |
** (reinitialiseAll(), rebind(); not those of deleted tenant databases).
|
|
70 |
** CA 20261029 purgeDepartedMember() waits for the grace of the departed member's membership lease first;
|
|
71 |
** heldKeys() names the current context's locks for the commit's per-owner lease; owner calls
|
|
72 |
** note the member which answered (MembershipLease.collector()).
|
| 70 |
73 |
*/
|
| 71 |
74 |
|
| 72 |
75 |
/*
|
| ... | ... | |
| 128 |
131 |
import java.util.*;
|
| 129 |
132 |
import java.util.concurrent.*;
|
| 130 |
133 |
import java.util.concurrent.atomic.*;
|
|
134 |
import java.util.function.BiConsumer;
|
|
135 |
import java.util.function.LongConsumer;
|
| 131 |
136 |
import java.util.logging.*;
|
| 132 |
137 |
|
| 133 |
138 |
import org.apache.geode.CancelException;
|
| ... | ... | |
| 958 |
963 |
}
|
| 959 |
964 |
|
| 960 |
965 |
/**
|
| 961 |
|
* Remove the locks of departed members from the local primary entries of every lock region; repeated
|
| 962 |
|
* after a few seconds to catch primaries moved here meanwhile. Called by {@link LockMembershipHandler}.
|
|
966 |
* Remove the locks of departed members from the local primary entries of every lock region, once the
|
|
967 |
* grace of the departed member's membership lease ended ({@link MembershipLease#GRACE_MS}: it may still be
|
|
968 |
* committing under them until then); repeated after a few seconds to catch primaries moved here meanwhile.
|
|
969 |
* Called by {@link LockMembershipHandler}.
|
| 963 |
970 |
*
|
| 964 |
971 |
* @param departed
|
| 965 |
972 |
* Id of the departed member, for the log.
|
| ... | ... | |
| 971 |
978 |
return;
|
| 972 |
979 |
}
|
| 973 |
980 |
|
| 974 |
|
purgeLocalPrimaries(departed);
|
| 975 |
981 |
try
|
| 976 |
982 |
{
|
|
983 |
// the departed member may still be committing under its locks until its membership lease expired
|
|
984 |
Thread.sleep(MembershipLease.GRACE_MS);
|
|
985 |
purgeLocalPrimaries(departed);
|
| 977 |
986 |
Thread.sleep(PURGE_AGAIN_MS);
|
| 978 |
987 |
}
|
| 979 |
988 |
catch (InterruptedException exc)
|
| ... | ... | |
| 1029 |
1038 |
}
|
| 1030 |
1039 |
|
| 1031 |
1040 |
/**
|
|
1041 |
* Name the record and table locks the current context holds, in every lock region of this server, for
|
|
1042 |
* the commit's per-owner membership lease ({@code MembershipLease.awaitCommit}).
|
|
1043 |
*
|
|
1044 |
* @param consumer
|
|
1045 |
* Takes the lock region and the key of each lock.
|
|
1046 |
* @param recordIds
|
|
1047 |
* Takes the record id of each record lock (not of table locks).
|
|
1048 |
*/
|
|
1049 |
public static void heldKeys(BiConsumer<Region<?, ?>, Object> consumer, LongConsumer recordIds)
|
|
1050 |
{
|
|
1051 |
for (OwnerLockManager<?> manager : MANAGERS.values())
|
|
1052 |
{
|
|
1053 |
Context<?> local = manager.context.get(false);
|
|
1054 |
Region<OwnerLockKey, OwnerLockEntry> region = manager.lockTable;
|
|
1055 |
if (local == null || region == null || local.locked.isEmpty())
|
|
1056 |
{
|
|
1057 |
continue;
|
|
1058 |
}
|
|
1059 |
|
|
1060 |
for (Map<? extends RecordIdentifier<?>, LockType> locks : local.locked.values())
|
|
1061 |
{
|
|
1062 |
for (RecordIdentifier<?> ident : locks.keySet())
|
|
1063 |
{
|
|
1064 |
consumer.accept(region, new OwnerLockKey(ident));
|
|
1065 |
Long id = ident.getRecordID();
|
|
1066 |
if (id != null)
|
|
1067 |
{
|
|
1068 |
recordIds.accept(id);
|
|
1069 |
}
|
|
1070 |
}
|
|
1071 |
}
|
|
1072 |
}
|
|
1073 |
}
|
|
1074 |
|
|
1075 |
/**
|
| 1032 |
1076 |
* Re-initialisation participant ({@link ClusterGate#ORDER_LOCKS}): on Geode's reconnected cache, register
|
| 1033 |
1077 |
* the membership listener again and bind every lock manager of {@link #MANAGERS} to its lock region again
|
| 1034 |
1078 |
* (the regions of the closed cache are dropped from {@link #REGIONS}; their leases are replaced by
|
| ... | ... | |
| 2926 |
2970 |
boolean fence = request.fence || attempt > 1;
|
| 2927 |
2971 |
OwnerLockFunction.Request sent = request.sent(CALL_SEQ.incrementAndGet(), fence);
|
| 2928 |
2972 |
long start = System.nanoTime();
|
|
2973 |
// the collector notes the owner which answers (its answer renews its membership lease)
|
| 2929 |
2974 |
ResultCollector<Object, List<Object>> rc = (ResultCollector<Object, List<Object>>)
|
| 2930 |
2975 |
FunctionService.onRegion(region)
|
| 2931 |
2976 |
.withFilter(Collections.singleton(request.key))
|
| 2932 |
2977 |
.setArguments(sent)
|
|
2978 |
.withCollector(MembershipLease.collector())
|
| 2933 |
2979 |
.execute(OwnerLockFunction.ID);
|
| 2934 |
2980 |
try
|
| 2935 |
2981 |
{
|
| 2936 |
|
-- a/src/com/goldencode/p2j/distributed/orm/OwnerUniqueFunction.java
|
|
2982 |
++ b/src/com/goldencode/p2j/distributed/orm/OwnerUniqueFunction.java
|
| ... | ... | |
| 8 |
8 |
** 001 ES 20261009 Initial version (#4369 owner-side unique tracker).
|
| 9 |
9 |
** ES 20261026 Liveness checks use LiveMembers (cached view), checked in purge() under the key's lock.
|
| 10 |
10 |
** ES 20261026 Removed an unused import.
|
|
11 |
** CA 20261029 A departed member's reservation is taken over or purged only after the grace of its
|
|
12 |
** membership lease (LiveMembers.holdsLocks()).
|
| 11 |
13 |
*/
|
| 12 |
14 |
|
| 13 |
15 |
/*
|
| ... | ... | |
| 300 |
302 |
return new Answer(key, Outcome.MINE, null);
|
| 301 |
303 |
}
|
| 302 |
304 |
|
| 303 |
|
if (!LiveMembers.isLive(stored.getMember()))
|
|
305 |
if (!LiveMembers.holdsLocks(stored.getMember()))
|
| 304 |
306 |
{
|
| 305 |
|
// a departed member's reservation never blocks anyone
|
|
307 |
// a departed member's reservation never blocks anyone once its membership lease expired
|
| 306 |
308 |
if (!region.replace(key, stored, holder))
|
| 307 |
309 |
{
|
| 308 |
310 |
return null;
|
| ... | ... | |
| 324 |
326 |
}
|
| 325 |
327 |
|
| 326 |
328 |
/**
|
| 327 |
|
* Remove the reservation of a key if it belongs to a member which left the cluster.
|
|
329 |
* Remove the reservation of a key if it belongs to a member which left the cluster (after the grace of its
|
|
330 |
* membership lease, {@link LiveMembers#holdsLocks}).
|
| 328 |
331 |
*
|
| 329 |
332 |
* @param region
|
| 330 |
333 |
* The reservation region.
|
| ... | ... | |
| 341 |
344 |
{
|
| 342 |
345 |
UniqueReservation stored = region.get(key);
|
| 343 |
346 |
|
| 344 |
|
return stored != null && !LiveMembers.isLive(stored.getMember()) && region.remove(key, stored);
|
|
347 |
return stored != null && !LiveMembers.holdsLocks(stored.getMember()) && region.remove(key, stored);
|
| 345 |
348 |
}
|
| 346 |
349 |
finally
|
| 347 |
350 |
{
|
| 348 |
|
-- a/src/com/goldencode/p2j/distributed/orm/OwnerUniqueIndex.java
|
|
351 |
++ b/src/com/goldencode/p2j/distributed/orm/OwnerUniqueIndex.java
|
| ... | ... | |
| 30 |
30 |
** again in a new epoch of the gate (re-initialisation participant); pending keys and keys
|
| 31 |
31 |
** carry the epoch they were made in: a reservation of an older epoch is never released or
|
| 32 |
32 |
** restored, and its state never replaces the state of the new epoch.
|
|
33 |
** CA 20261029 purgeDepartedMember() waits for the grace of the departed member's membership lease first;
|
|
34 |
** pendingKeys() names the session's pending keys for the commit's per-owner lease; owner
|
|
35 |
** calls note the member which answered (MembershipLease.collector()).
|
| 33 |
36 |
*/
|
| 34 |
37 |
|
| 35 |
38 |
/*
|
| ... | ... | |
| 88 |
91 |
package com.goldencode.p2j.distributed.orm;
|
| 89 |
92 |
|
| 90 |
93 |
|
|
94 |
import java.lang.ref.*;
|
| 91 |
95 |
import java.util.*;
|
| 92 |
96 |
import java.util.concurrent.*;
|
| 93 |
97 |
import java.util.concurrent.locks.*;
|
|
98 |
import java.util.function.BiConsumer;
|
| 94 |
99 |
import java.util.logging.*;
|
| 95 |
100 |
|
| 96 |
101 |
import org.apache.geode.CancelException;
|
| ... | ... | |
| 171 |
176 |
/** End-of-transaction removals of the current session, not sent yet. */
|
| 172 |
177 |
private static final ThreadLocal<List<Release>> REMOVED = ThreadLocal.withInitial(ArrayList::new);
|
| 173 |
178 |
|
|
179 |
/** Every index of this server (weakly held, by identity), for {@link #pendingKeys} (guarded by itself). */
|
|
180 |
private static final List<WeakReference<OwnerUniqueIndex>> ALL = new ArrayList<>();
|
|
181 |
|
| 174 |
182 |
/** Reservation regions by database name (lower case). */
|
| 175 |
183 |
private static final Map<String, Region<ReservedKey, UniqueReservation>> REGIONS =
|
| 176 |
184 |
new ConcurrentHashMap<>();
|
| ... | ... | |
| 233 |
241 |
super(fieldMap, NO_LOCK, props);
|
| 234 |
242 |
this.uniqueId = uniqueId;
|
| 235 |
243 |
this.dbName = dbName;
|
|
244 |
|
|
245 |
synchronized (ALL)
|
|
246 |
{
|
|
247 |
ALL.removeIf(r -> r.get() == null);
|
|
248 |
ALL.add(new WeakReference<>(this));
|
|
249 |
}
|
|
250 |
}
|
|
251 |
|
|
252 |
/**
|
|
253 |
* Name the unique keys pending in open transactions, of the current epoch,
|
|
254 |
* in every reservation region, of the given records, for the commit's per-owner membership lease
|
|
255 |
* ({@code MembershipLease.awaitCommit}): the records the current session holds locked (a session changes
|
|
256 |
* or creates a record only under its lock), so the keys of other sessions' records are left out (a
|
|
257 |
* record id of another table may add a key, which only adds a member to wait for).
|
|
258 |
*
|
|
259 |
* @param recordIds
|
|
260 |
* The record ids.
|
|
261 |
* @param consumer
|
|
262 |
* Takes the reservation region and the reserved key.
|
|
263 |
*/
|
|
264 |
public static void pendingKeys(Set<Long> recordIds, BiConsumer<Region<?, ?>, Object> consumer)
|
|
265 |
{
|
|
266 |
if (recordIds.isEmpty())
|
|
267 |
{
|
|
268 |
return;
|
|
269 |
}
|
|
270 |
|
|
271 |
List<OwnerUniqueIndex> indexes = new ArrayList<>();
|
|
272 |
synchronized (ALL)
|
|
273 |
{
|
|
274 |
for (WeakReference<OwnerUniqueIndex> r : ALL)
|
|
275 |
{
|
|
276 |
OwnerUniqueIndex index = r.get();
|
|
277 |
if (index != null)
|
|
278 |
{
|
|
279 |
indexes.add(index);
|
|
280 |
}
|
|
281 |
}
|
|
282 |
}
|
|
283 |
|
|
284 |
long epoch = ClusterGate.epoch();
|
|
285 |
for (OwnerUniqueIndex index : indexes)
|
|
286 |
{
|
|
287 |
Region<ReservedKey, UniqueReservation> r = index.region;
|
|
288 |
if (r == null || index.boundEpoch != epoch || index.pending.isEmpty())
|
|
289 |
{
|
|
290 |
continue;
|
|
291 |
}
|
|
292 |
|
|
293 |
for (Long id : recordIds)
|
|
294 |
{
|
|
295 |
Pending p = index.pending.get(id);
|
|
296 |
if (p != null && p.epoch == epoch && !p.reserved.hasNulls())
|
|
297 |
{
|
|
298 |
consumer.accept(r, p.reserved);
|
|
299 |
}
|
|
300 |
}
|
|
301 |
}
|
| 236 |
302 |
}
|
| 237 |
303 |
|
| 238 |
304 |
/**
|
| ... | ... | |
| 422 |
488 |
Set<ReservedKey> filter,
|
| 423 |
489 |
OwnerUniqueFunction.Request request)
|
| 424 |
490 |
{
|
|
491 |
// the collector notes the owners which answer (their answers renew their membership lease)
|
| 425 |
492 |
ResultCollector<Object, List<Object>> rc = (ResultCollector<Object, List<Object>>)
|
| 426 |
493 |
FunctionService.onRegion(region)
|
| 427 |
494 |
.withFilter(filter)
|
| 428 |
495 |
.setArguments(request)
|
|
496 |
.withCollector(MembershipLease.collector())
|
| 429 |
497 |
.execute(OwnerUniqueFunction.ID);
|
| 430 |
498 |
|
| 431 |
499 |
Map<ReservedKey, OwnerUniqueFunction.Answer> answers = new HashMap<>();
|
| ... | ... | |
| 1298 |
1366 |
}
|
| 1299 |
1367 |
|
| 1300 |
1368 |
/**
|
| 1301 |
|
* Remove a departed member's reservations from the keys this member owns, now and again a little
|
| 1302 |
|
* later (key ownership may still be moving).
|
|
1369 |
* Remove a departed member's reservations from the keys this member owns, once the grace of its
|
|
1370 |
* membership lease ended ({@link MembershipLease#GRACE_MS}), and again a little later (key ownership may
|
|
1371 |
* still be moving).
|
| 1303 |
1372 |
*
|
| 1304 |
1373 |
* @param departed
|
| 1305 |
1374 |
* Id of the departed member.
|
| ... | ... | |
| 1311 |
1380 |
return;
|
| 1312 |
1381 |
}
|
| 1313 |
1382 |
|
| 1314 |
|
purgeLocalPrimaries(departed);
|
| 1315 |
1383 |
try
|
| 1316 |
1384 |
{
|
|
1385 |
// the departed member may still be committing under its locks until its membership lease expired
|
|
1386 |
Thread.sleep(MembershipLease.GRACE_MS);
|
|
1387 |
purgeLocalPrimaries(departed);
|
| 1317 |
1388 |
Thread.sleep(PURGE_AGAIN_MS);
|
| 1318 |
1389 |
}
|
| 1319 |
1390 |
catch (InterruptedException exc)
|
| 1320 |
|
-- a/src/com/goldencode/p2j/persist/FastFindCache.java
|
|
1391 |
++ b/src/com/goldencode/p2j/persist/FastFindCache.java
|
| ... | ... | |
| 61 |
61 |
** CA 20261029 Added getPermanentInstance(Type, String): the lookup by database type and name (the meta
|
| 62 |
62 |
** database has the primary's name), used by the cluster commit notice; added
|
| 63 |
63 |
** invalidateAllPermanent() (notices missed while this server was not a cluster member).
|
|
64 |
** CA 20261029 Removed the per-change invalidation broadcast to the other servers (broadcast(), the
|
|
65 |
** name-only getPermanentInstance(String), setIsFromRemoteRequest()): the cluster commit
|
|
66 |
** notice is the only invalidation across the servers.
|
| 64 |
67 |
*/
|
| 65 |
68 |
|
| 66 |
69 |
/*
|
| ... | ... | |
| 119 |
122 |
package com.goldencode.p2j.persist;
|
| 120 |
123 |
|
| 121 |
124 |
import com.goldencode.cache.*;
|
| 122 |
|
import com.goldencode.p2j.distributed.cache.*;
|
| 123 |
|
import com.goldencode.p2j.distributed.config.*;
|
| 124 |
125 |
import com.goldencode.cache.Cache.Entry;
|
| 125 |
126 |
import com.goldencode.p2j.persist.orm.*;
|
| 126 |
127 |
import com.goldencode.p2j.security.*;
|
| ... | ... | |
| 128 |
129 |
import com.goldencode.p2j.util.logging.*;
|
| 129 |
130 |
|
| 130 |
131 |
import com.goldencode.util.ResizableHashMap;
|
| 131 |
|
import org.apache.geode.cache.execute.*;
|
| 132 |
|
import org.apache.geode.distributed.*;
|
| 133 |
132 |
|
| 134 |
133 |
import java.lang.ref.WeakReference;
|
| 135 |
134 |
import java.util.*;
|
| ... | ... | |
| 212 |
211 |
/** Differentiate between permanent and temporary tables and caches. */
|
| 213 |
212 |
private final boolean isPermanent;
|
| 214 |
213 |
|
| 215 |
|
/** Flag indicating distributed mode */
|
| 216 |
|
private final boolean distributedMode;
|
| 217 |
|
|
| 218 |
214 |
/**
|
| 219 |
215 |
* Flag indicating this instance is switched off: it is for a permanent database and the directory
|
| 220 |
216 |
* node {@code persistence/fast-find-cache} is {@code false}. A disabled instance never caches, never
|
| 221 |
|
* invalidates and never broadcasts.
|
|
217 |
* invalidates.
|
| 222 |
218 |
*/
|
| 223 |
219 |
private final boolean disabled;
|
| 224 |
220 |
|
| ... | ... | |
| 230 |
226 |
private final AtomicLong generation = new AtomicLong();
|
| 231 |
227 |
|
| 232 |
228 |
/**
|
| 233 |
|
* Thread-local flag to mark threads that are handling remote invalidation requests.
|
| 234 |
|
*/
|
| 235 |
|
private static final ThreadLocal<Boolean> isFromRemoteRequest = ThreadLocal.withInitial(() -> false);
|
| 236 |
|
|
| 237 |
|
/**
|
| 238 |
229 |
* Constructor for internal use.
|
| 239 |
230 |
*
|
| 240 |
231 |
* @param isPermanent
|
| ... | ... | |
| 243 |
234 |
private FastFindCache(boolean isPermanent, Database database)
|
| 244 |
235 |
{
|
| 245 |
236 |
this.isPermanent = isPermanent;
|
| 246 |
|
this.distributedMode = ClusterConfig.isEnabled();
|
| 247 |
237 |
this.database = database;
|
| 248 |
238 |
this.disabled = isPermanent && !permanentEnabled;
|
| 249 |
239 |
|
| ... | ... | |
| 258 |
248 |
* for it. For the permanent databases, the cache can be switched off with the directory boolean node
|
| 259 |
249 |
* {@code persistence/fast-find-cache} (default {@code true}, read once at class load). The instance
|
| 260 |
250 |
* returned then is inert: {@code get} always misses, {@code put} and all {@code invalidate} variants
|
| 261 |
|
* do nothing (and nothing is broadcast to other servers), and no state is accumulated. The temp-table
|
| 262 |
|
* instances are not affected by this setting.
|
|
251 |
* do nothing, and no state is accumulated. The temp-table instances are not affected by this setting.
|
| 263 |
252 |
*
|
| 264 |
253 |
* @param temporary
|
| 265 |
254 |
* Use {@code true} to get the instance object for a temp-table database. They are context-local
|
| ... | ... | |
| 307 |
296 |
}
|
| 308 |
297 |
|
| 309 |
298 |
/**
|
| 310 |
|
* Get the existing instance of a permanent database by the database's name, without a user context
|
| 311 |
|
* (for the cluster invalidation functions, which run on Geode threads).
|
| 312 |
|
*
|
| 313 |
|
* @param dbName
|
| 314 |
|
* The name of the database, as {@link Database#getName()} returns it.
|
| 315 |
|
*
|
| 316 |
|
* @return The instance, or {@code null} if this server has none (it then caches nothing for that
|
| 317 |
|
* database).
|
| 318 |
|
*/
|
| 319 |
|
public static FastFindCache getPermanentInstance(String dbName)
|
| 320 |
|
{
|
| 321 |
|
for (Map.Entry<Database, FastFindCache> e : permanentInstances.entrySet())
|
| 322 |
|
{
|
| 323 |
|
if (e.getKey().getName().equals(dbName))
|
| 324 |
|
{
|
| 325 |
|
return e.getValue();
|
| 326 |
|
}
|
| 327 |
|
}
|
| 328 |
|
|
| 329 |
|
return null;
|
| 330 |
|
}
|
| 331 |
|
|
| 332 |
|
/**
|
| 333 |
299 |
* Get the existing instance of a permanent database by the database's type and name, without a user
|
| 334 |
300 |
* context (for the cluster commit notice, which runs on Geode threads). The type tells apart a primary
|
| 335 |
301 |
* database and the meta database, which have the same name.
|
| ... | ... | |
| 488 |
454 |
}
|
| 489 |
455 |
|
| 490 |
456 |
/**
|
| 491 |
|
* Marks the current thread as a remote-request handler. Once set, {@code broadcast()} becomes
|
| 492 |
|
* a no-op for this thread, preventing infinite re-distribution of invalidation messages.
|
| 493 |
|
*/
|
| 494 |
|
public static void setIsFromRemoteRequest()
|
| 495 |
|
{
|
| 496 |
|
isFromRemoteRequest.set(true);
|
| 497 |
|
}
|
| 498 |
|
|
| 499 |
|
/**
|
| 500 |
457 |
* Query the cache for a value.
|
| 501 |
458 |
*
|
| 502 |
459 |
* @param k
|
| ... | ... | |
| 746 |
703 |
});
|
| 747 |
704 |
}
|
| 748 |
705 |
}
|
| 749 |
|
|
| 750 |
|
if (distributedMode && isPermanent)
|
| 751 |
|
{
|
| 752 |
|
broadcast(new FastFindCacheInvalidationRequest.InvalidateAllIndicesForTable(database.getName(),
|
| 753 |
|
dmoUid));
|
| 754 |
|
}
|
| 755 |
706 |
}
|
| 756 |
707 |
|
| 757 |
708 |
/**
|
| ... | ... | |
| 928 |
879 |
}
|
| 929 |
880 |
}
|
| 930 |
881 |
}
|
| 931 |
|
|
| 932 |
|
if (distributedMode && isPermanent)
|
| 933 |
|
{
|
| 934 |
|
broadcast(new FastFindCacheInvalidationRequest.InvalidateIndices(
|
| 935 |
|
database.getName(), dmoUid, unique, nonUnique));
|
| 936 |
|
|
| 937 |
|
}
|
| 938 |
882 |
}
|
| 939 |
883 |
|
| 940 |
884 |
/**
|
| ... | ... | |
| 957 |
901 |
{
|
| 958 |
902 |
l3.clear();
|
| 959 |
903 |
}
|
| 960 |
|
|
| 961 |
|
if (distributedMode && isPermanent)
|
| 962 |
|
{
|
| 963 |
|
broadcast(new FastFindCacheInvalidationRequest.InvalidateIndex(
|
| 964 |
|
database.getName(), dmoUid, idxUid));
|
| 965 |
|
}
|
| 966 |
904 |
}
|
| 967 |
905 |
}
|
| 968 |
906 |
|
| ... | ... | |
| 982 |
920 |
map.forEach((idxId, cache) -> cache.clear());
|
| 983 |
921 |
}
|
| 984 |
922 |
}
|
| 985 |
|
|
| 986 |
|
if (distributedMode && isPermanent)
|
| 987 |
|
{
|
| 988 |
|
broadcast(new FastFindCacheInvalidationRequest.InvalidateDmoList(
|
| 989 |
|
database.getName(), dmoList));
|
| 990 |
|
}
|
| 991 |
923 |
}
|
| 992 |
924 |
|
| 993 |
925 |
/**
|
| 994 |
|
* Broadcasts a cache invalidation request to all other FWD server members in the distributed
|
| 995 |
|
* system. Non-server members (e.g. Geode locators) are excluded, since they can neither
|
| 996 |
|
* deserialize nor execute FWD functions.
|
| 997 |
|
* <p>
|
| 998 |
|
* To prevent cyclic broadcasting (echoes) across the cluster, this method immediately
|
| 999 |
|
* returns if the current execution context was triggered by a remote request.
|
| 1000 |
|
* If there are other active FWD servers in the Geode cluster, it uses the
|
| 1001 |
|
* {@code FunctionService} to execute the {@code FastFindCacheInvalidationFunction}
|
| 1002 |
|
* on those peers, passing the invalidation payload.
|
| 1003 |
|
* </p>
|
| 1004 |
|
*
|
| 1005 |
|
* @param request
|
| 1006 |
|
* The payload containing the specific cache invalidation details to broadcast.
|
| 1007 |
|
*/
|
| 1008 |
|
private void broadcast(FastFindCacheInvalidationRequest request)
|
| 1009 |
|
{
|
| 1010 |
|
if (isFromRemoteRequest.get())
|
| 1011 |
|
{
|
| 1012 |
|
return;
|
| 1013 |
|
}
|
| 1014 |
|
|
| 1015 |
|
if (com.goldencode.p2j.distributed.cache.CommitNotice.isEnabled())
|
| 1016 |
|
{
|
| 1017 |
|
// the other servers learn of committed changes from the commit notice; these invalidations are
|
| 1018 |
|
// mostly sent before the commit (useless there: the change is not visible yet) and name the table
|
| 1019 |
|
// by this JVM's DMO uid, which is another table, or none, on another server
|
| 1020 |
|
return;
|
| 1021 |
|
}
|
| 1022 |
|
|
| 1023 |
|
Set<DistributedMember> otherMembers = GeodeConfig.getOtherServerMembers();
|
| 1024 |
|
|
| 1025 |
|
if (otherMembers.isEmpty())
|
| 1026 |
|
{
|
| 1027 |
|
return;
|
| 1028 |
|
}
|
| 1029 |
|
|
| 1030 |
|
FunctionService.onMembers(otherMembers)
|
| 1031 |
|
.setArguments(request)
|
| 1032 |
|
.execute(FastFindCacheInvalidationFunction.class.getName());
|
| 1033 |
|
}
|
| 1034 |
|
|