Le 06/10/2026 à 20:25, Jerry D a écrit :
On 10/6/26 3:22 AM, Mikael Morin wrote:
Le 06/10/2026 à 02:58, Jerry D a écrit :
On 10/5/26 10:04 AM, Jerry D wrote:
See the attached patch. In my campaign to identify caf related bugs I discovered this one.

Regression tested under heavy loads.

OK for mainline?

Regards,

Jerry
I am working on ways to create LLM assisted patches more human readable. Using Claude, and I suspect other LLMs, one can set up rules and guidelines to follow. The attached V2 of the subject patch is essentially identical with an improved commit message that better explanations of the problem and the fix. There is a bit of a guide here on how to review all this as well.

How the hang happens: image 1 waits in SYNC IMAGES ([2, 3]) when image
3 stops. Image 2 then executes SYNC IMAGES ([1, 3]), finds image 3
stopped and returns with STAT_STOPPED_IMAGE without synchronizing, so
image 1 is not released. FAIL IMAGE leads to the same hang.

The point to check is the order in sync_table_terminated: under the
table lock it releases the waiting images and takes back their
unmatched counts, and only then stores the new status. Outside the
WIN32 supervisor, which this patch does not change, every change of an
image's status to stopped or failed goes through it.

Reading guide: start with sync.h, the new waiting and aborted fields
and what sync_table and sync_table_terminated promise. Then those two
functions in sync.c. In shmem.c, _gfortran_caf_sync_images drops its
early return for a terminated image and sets STAT= only when sync_table
reports a terminated image. The other shmem.c and supervisor.c hunks
route the status changes through sync_table_terminated, which replaces
the supervisor's wake-up of all waiting images. set_table_parts and
the sync_init changes only lay out the enlarged table.

Regression tested on x86_64.

OK for mainline?

Regards,

Jerry

PS Feedback on the format of this email is greatly appreciated.

---

libgfortran: [PR127684] caf_shmem: SYNC IMAGES hang on
  terminated image

SYNC IMAGES hung when an image of the image set stopped or failed while
another image of the set was waiting in the statement.

An image that found a stopped or failed image in its set returned at once with STAT=, without synchronizing, so an image already waiting for it was
never released.  The supervisor also woke the waiting images without
holding the table lock, so a wakeup could be lost.

Each image now records the set it waits for.  Images that stop or fail,
and the POSIX supervisor when it reaps one, set the status through the
new sync_table_terminated, which holds the table lock.  For a stopped
image it releases the images waiting for it and takes back the counts
they raised that no partner has matched, before it stores the status,
so an image that sees the stop cannot pair with the aborted statement.
A failed image is skipped and the other images of the set synchronize
(F2023 11.7.11).

Assisted-by: Claude Opus 5.5

     PR libfortran/127684

libgfortran/ChangeLog:

     * caf/shmem.c (_gfortran_caf_sync_images): Do not return early for
     a stopped or failed image.  Check the images only when one of them
     terminated.
     (mark_stopped): Use sync_table_terminated.
     (_gfortran_caf_fail_image): Likewise.
     * caf/shmem/supervisor.c (supervisor_main_loop): Likewise.  Do not
     signal all images waiting in sync_table.
     * caf/shmem/sync.c (set_table_parts): New function.
     (sync_init): Use it.
     (sync_init_supervisor): Allocate the image sets of the waiting
     images and the aborted flags.
     (stopped_image_in): New function.
     (sync_table): Return whether an image of the set terminated.  Only
     synchronize memory when an image of the set stopped.  Record the
     image set and end the wait when aborted.
     (sync_table_terminated): New function.
     * caf/shmem/sync.h (sync_t): Add waiting and aborted.
     (sync_table): Return bool.
     (sync_table_terminated): Declare.

gcc/testsuite/ChangeLog:

     * gfortran.dg/coarray/sync_images_failed_1.f90: New test.
     * gfortran.dg/coarray/sync_images_stopped_2.f90: New test.
---

+void
+sync_table_terminated (sync_t *si, int image, bool stopped)
As far as I can see, the function doesn't impact only sync_table as it also updates the supervisor tables, so please remove sync_table from the name (notify_image_terminated, image_terminated, acknowledge_image_terminated or similar).
+{
+  volatile int *table = si->table;
+  const size_t img_c = local->total_num_images;
+
+  lock_table (si);
+  for (size_t j = 0; j < img_c; ++j)
+    {
+      if (!si->waiting[image + img_c * j])
+    continue;
+      if (stopped)
+    {
+      /* Take back the counts of J that its partners have not matched.
+         IMAGE is marked stopped only below, so no image that sees the
+         stop can pair with J's aborted SYNC IMAGES.  */
+      for (size_t k = 0; k < img_c; ++k)
+        if (si->waiting[k + img_c * j]
+        && table[k + img_c * j] > table[j + img_c * k])
+          --table[k + img_c * j];
It took me some time to understand why this loop was necessary.  I finally convinced myself that it makes sense, but I think we can do without it if...

+      si->aborted[j] = 1;
+    }
+      caf_shmem_cond_signal (&si->triggers[j]);
+    }
+  /* The images woken above need the table lock to return from their wait,> +     so they see this count and the new status.  */
+  atomic_fetch_add (stopped ? &this_image.supervisor->finished_images
+                : &this_image.supervisor->failed_images, 1);
+  this_image.supervisor->images[image].status
+    = stopped ? IMAGE_SUCCESS : IMAGE_FAILED;
   unlock_table (si);
 }


@@ -106,6 +131,16 @@ sync_table (sync_t *si, int *images, int size)
     }

   lock_table (si);
+  /* With a stopped image in the set this only has the effect of SYNC
+     MEMORY; failed images are skipped (F2023 11.7.11).  */
+  if (stopped_image_in (images, size))
+    {
+      unlock_table (si);
+      return true;
+    }
... this is moved further down...

+  for (i = 0; i < size; ++i)
+    si->waiting[images[i] + img_c * this_image.image_num] = 1;
+  si->aborted[this_image.image_num] = 0;
   for (i = 0; i < size; ++i)
     {
       if (this_image.supervisor->images[images[i]].status != IMAGE_OK)
... after this loop.  Instead of decreasing the count of already arrived images, we would not skip the increase of the count as images arrive to the barrier.


@@ -115,6 +150,9 @@ sync_table (sync_t *si, int *images, int size)
     }
   for (;;)
     {
+      /* An image of the set stopped, see sync_table_terminated.  */
+      if (si->aborted[this_image.image_num])
+    break;
The information present in the aborted array seems to be redundant with the status of images shared with the supervisor.
       for (i = 0; i < size; ++i)
     if (this_image.supervisor->images[images[i]].status == IMAGE_OK
As the status is already queried in the main loop, we can just as well handle status different from IMAGE_OK here, so that the aborted array is no longer necessary.
         && table[images[i] + img_c * this_image.image_num]


Next there is the waiting array that I would like to remove as well.
As far as I can see, with the count decrease loop removed and the aborted array removed, the waiting array remains useful to selectively wake the needed images in sync_table_terminated.  But it requires updating it in every sync image, just to support a premature image stop.  Can we just wake every image in the team on image stop and remove the waiting array and the associated book keeping in the main synchronisation code?


The explaining text is an improvement I think, but I had already a clear picture of the patch in mind when I started reading it.  Let's see how it helps with a completely unknown patch.
Thanks for your work.

Hi Mikael,

Thanks for the review.  The attached v3 takes all of your suggestions,
and the patch is much smaller for it.

Changes since v2:

- sync_table_terminated is renamed notify_image_terminated.
- sync_table counts the statement for every active image of the set
   before it checks for a stopped image, so the counts of the images
   that remain active always agree.  The loop that took back unmatched
   counts is gone.
- The wait loop ends when the status of an image of the set is stopped,
   so the aborted array is gone.
- notify_image_terminated wakes every image, so the waiting array is
   gone as well.  The table is back to N x N and set_table_parts is
   gone.

Counting the statement also changes one behaviour, for the better I
think.  F2023 11.7.4 pairs SYNC IMAGES statements by how many times
each image has executed one with the other image in its set, and a
statement that finds a stopped image has still been executed.  v2 did
not count it, so when image 1 is in SYNC IMAGES (2) and image 2 runs
SYNC IMAGES ([1, 3]) after image 3 stopped, image 1 kept waiting for a
later statement of image 2.  The new test sync_images_stopped_3.f90
covers this case; it hangs with v2.

Reading guide: start with sync_table in sync.c, the count loop and the
stopped-image check at the top of the wait loop, then
notify_image_terminated below it.  In shmem.c,
_gfortran_caf_sync_images drops its early return for a terminated
image and sets STAT= only when sync_table reports one.  The other
shmem.c and supervisor.c hunks route the status changes through
notify_image_terminated, which replaces the supervisor's wake-up
without the table lock.

Note: The WIN32 path is not changed. I do not have a Wondows based system to test with at the moment. It will have to be a later smaller patch.

Tested with the three new tests, and with a matrix of 66 cases (STOP,
FAIL IMAGE and a killing signal against SYNC IMAGES with a list and
with *, in the initial team and in a child team), 500 runs quiet and
500 under full CPU load: no hangs, and the right STAT= in every run.
Regression tested on x86_64.

OK for mainline?

Yes, thanks for it.

Reply via email to