Skip to content

Commit 8c05696

Browse files
committed
tst_zero_len_recvar.c - zero-length request in a collective blocking put API
This test program was provided by Blaise Bourdin (and revised a little). It calls a collective blocking put API to write to a record variable where one of the processes makes a zero-length request. When writing to a record variable, PnetCDF calls MPI_Allreduce to update and sync the number of records among all processes. If a subset of the processes has a zero-length request, these processes may not participate the call to MPI_Allreduce, causing program to hang. See PR #239.
1 parent 4c6df25 commit 8c05696

2 files changed

Lines changed: 167 additions & 1 deletion

File tree

‎test/testcases/Makefile.am‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ check_PROGRAMS = file_create_open \
104104
put_all_kinds \
105105
redef1 \
106106
iput_all_kinds \
107-
tst_version
107+
tst_version \
108+
tst_zero_len_recvar
108109

109110
M4_SRCS = put_all_kinds.m4 \
110111
erange_fill.m4 \
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/******************************************************************************
2+
*
3+
* Copyright (C) 2026, Northwestern University and Argonne National Laboratory
4+
* See COPYRIGHT notice in top-level directory.
5+
*
6+
*****************************************************************************/
7+
8+
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
9+
* This program tests a collective write to a record variable in which one of
10+
* the processes has nothing to contribute, i.e. it passes count == 0. Every
11+
* process still calls the collective API, so the call is legal.
12+
*
13+
* Such a request must not opt out of the collective synchronization of numrecs
14+
* that follows the I/O. If it does, the processes that do have data to write
15+
* are left calling an MPI_Allreduce() alone and the communicator goes out of
16+
* step, which shows up as a wrong error code, as corruption of a later
17+
* collective call, or as a hang.
18+
*
19+
* Fixed-size variables take no part in the numrecs synchronization and are
20+
* therefore not affected; the record variable is the case worth guarding.
21+
*
22+
* To compile:
23+
* mpicc -O2 tst_zero_len_recvar.c -o tst_zero_len_recvar -lpnetcdf
24+
*
25+
* % mpiexec -n 4 ./tst_zero_len_recvar testfile.nc
26+
*
27+
* This test program was contributed by Blaise Bourdin. See the github pull
28+
* request in https://github.com/Parallel-NetCDF/PnetCDF/pull/238
29+
*
30+
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
31+
32+
#include <stdio.h>
33+
#include <stdlib.h>
34+
#include <string.h>
35+
#include <libgen.h> /* basename() */
36+
#include <pnetcdf.h>
37+
38+
#include <testutils.h>
39+
40+
#define NDBL 4 /* number of doubles written per process */
41+
42+
static
43+
int test_io(const char *out_path,
44+
const char *in_path, /* ignored */
45+
int format,
46+
int coll_io,
47+
MPI_Info info)
48+
{
49+
char val[MPI_MAX_INFO_VAL];
50+
int i, err, nerrs=0, ncid, dimid[2], varid, rank, nprocs, is_empty;
51+
int flag, bb_enabled=0;
52+
MPI_Offset start[2], count[2], numrecs;
53+
double buf[NDBL], rbuf[NDBL];
54+
55+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
56+
MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
57+
58+
/* check whether burst buffering is enabled */
59+
MPI_Info_get(info, "nc_burst_buf", MPI_MAX_INFO_VAL - 1, val, &flag);
60+
if (flag && strcasecmp(val, "enable") == 0) {
61+
bb_enabled = 1;
62+
/* does not work for NetCDF4 files when burst-buffering is enabled */
63+
if (format == NC_FORMAT_NETCDF4 || format == NC_FORMAT_NETCDF4_CLASSIC)
64+
return 0;
65+
}
66+
67+
/* the last process contributes nothing; with a single process there is
68+
* nobody left to write, so keep its request non-empty
69+
*/
70+
is_empty = (nprocs > 1 && rank == nprocs - 1);
71+
72+
for (i=0; i<NDBL; i++) buf[i] = (double)(rank * NDBL + i);
73+
74+
err = ncmpi_set_default_format(format, NULL); CHECK_ERR
75+
76+
err = ncmpi_create(MPI_COMM_WORLD, out_path, NC_CLOBBER, info, &ncid); CHECK_ERR
77+
err = ncmpi_def_dim(ncid, "time", NC_UNLIMITED, &dimid[0]); CHECK_ERR
78+
err = ncmpi_def_dim(ncid, "nelem", nprocs * NDBL, &dimid[1]); CHECK_ERR
79+
err = ncmpi_def_var(ncid, "v", NC_DOUBLE, 2, dimid, &varid); CHECK_ERR
80+
err = ncmpi_set_fill(ncid, NC_FILL, NULL); CHECK_ERR
81+
err = ncmpi_enddef(ncid); CHECK_ERR
82+
83+
/* ncmpi_fill_var_rec() is not supported when NetCDF4 driver is used */
84+
err = ncmpi_fill_var_rec(ncid, varid, 0);
85+
if (err != NC_ENOTSUPPORT) CHECK_ERR
86+
87+
/* all processes write into record 0 */
88+
start[0] = 0;
89+
count[0] = 1;
90+
start[1] = (MPI_Offset)rank * NDBL;
91+
count[1] = NDBL;
92+
if (is_empty) { start[1] = 0; count[1] = 0; }
93+
94+
if (coll_io) {
95+
err = ncmpi_put_vara_double_all(ncid, varid, start, count, buf);
96+
CHECK_ERR
97+
}
98+
else {
99+
err = ncmpi_begin_indep_data(ncid); CHECK_ERR
100+
err = ncmpi_put_vara_double(ncid, varid, start, count, buf); CHECK_ERR
101+
err = ncmpi_end_indep_data(ncid); CHECK_ERR
102+
}
103+
104+
if (bb_enabled)
105+
err = ncmpi_flush(ncid);
106+
107+
/* one record must have been created, and all processes must agree */
108+
err = ncmpi_inq_dimlen(ncid, dimid[0], &numrecs); CHECK_ERR
109+
if (numrecs != 1) {
110+
printf("%d Error at %s:%d: expecting numrecs 1 but got %lld\n",
111+
rank,__FILE__, __LINE__, numrecs);
112+
nerrs++;
113+
}
114+
115+
err = ncmpi_close(ncid); CHECK_ERR
116+
117+
/* read back and check the contents written by the non-empty processes */
118+
err = ncmpi_open(MPI_COMM_WORLD, out_path, NC_NOWRITE, info, &ncid); CHECK_ERR
119+
err = ncmpi_inq_varid(ncid, "v", &varid); CHECK_ERR
120+
121+
if (!is_empty) {
122+
for (i=0; i<NDBL; i++) rbuf[i] = -1.0;
123+
err = ncmpi_get_vara_double_all(ncid, varid, start, count, rbuf); CHECK_ERR
124+
for (i=0; i<NDBL; i++) {
125+
if (rbuf[i] != buf[i]) {
126+
printf("Error at %s:%d: expecting v[%lld] %g but got %g\n",
127+
__FILE__, __LINE__, start[1]+i, buf[i], rbuf[i]);
128+
nerrs++;
129+
break;
130+
}
131+
}
132+
}
133+
else {
134+
/* still a collective call, with nothing to read */
135+
err = ncmpi_get_vara_double_all(ncid, varid, start, count, rbuf); CHECK_ERR
136+
}
137+
138+
err = ncmpi_close(ncid); CHECK_ERR
139+
140+
return nerrs;
141+
}
142+
143+
int main(int argc, char **argv) {
144+
145+
int err;
146+
loop_opts opt;
147+
148+
MPI_Init(&argc, &argv);
149+
150+
opt.num_fmts = sizeof(nc_formats) / sizeof(int);
151+
opt.formats = nc_formats;
152+
opt.ina = 2; /* enable and disable intra-node aggregation */
153+
opt.drv = 2; /* test GIO and MPI-IO driver */
154+
opt.ibuf = 2; /* enable and disable hint nc_ibuf_size */
155+
opt.bb = 2; /* enable and disable burst-buffering feature */
156+
opt.mod = 2; /* collective and independent data mode */
157+
opt.hdr_diff = true; /* run ncmpidiff for file header */
158+
opt.var_diff = true; /* run ncmpidiff for variables */
159+
160+
err = tst_main(argc, argv, "0-len req to record var", opt,
161+
test_io);
162+
163+
MPI_Finalize();
164+
return err;
165+
}

0 commit comments

Comments
 (0)