Repository: Unidata/netcdf-c (c)

Issue:
Remove all references to  very obsolete _CRAYMPP and LOCKNUMREC macros
Back in olden, olden times, the Cray people did some exciting new things with multiprocessing. And netcdf was there to support that.

Those exciting early developments eventually evolved into MPI, which is now universally used for HPC, including on all Cray machines. This has been the case for more than 20 years.

So there is no longer any need for the old code that supported old Cray multiprocessing. Even if it would still work (which it won't) it is written around the concept of one MPI communicator, and modern HPC applications routinely use multiple MPI communicators at the same time. MPI depends on this capability, and netCDF supports it with our parallel code.

So we can remove these old macros in code like this:

```
void
free_NC(NC *ncp)
{
    if(ncp == NULL)
        return;
    if(ncp->path)
        free(ncp->path);
    if(ncp->model)
        free(ncp->model);
    /* We assume caller has already cleaned up ncp->dispatchdata */
#if _CRAYMPP && defined(LOCKNUMREC)
    shfree(ncp);
#else
    free(ncp);
#endif /* _CRAYMPP && LOCKNUMREC */
```

These will be easy to remove, and will have no effect on testing, since this code is currently untested and would probably break if it were tested.

Pull request description:
Remove obsolete _CRAYMPP and LOCKNUMREC macros
Fixes #1459.

The obsolete macros _CRAYMPP and LOCKNUMREC were in use on an ancient version of a Cray. They would no longer work on modern Crays, which use MPI and the nc_open_par/nc_create_par functions for parallel I/O.

This will have no effect on existing users, since no could build with these macros - they are not supported in the configure.ac file.

This PR contains PR #1463, and should be inspected after that one is merged.

Patch:
diff --git a/Makefile.am b/Makefile.am
index 876725885..61d3f523e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -21,6 +21,11 @@ test-driver-verbose test_common.in
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfig_DATA = netcdf.pc
 
+# Does the user want to build and run unit tests?
+if BUILD_UNIT_TESTS
+UNIT_TEST = unit_test
+endif # BUILD_UNIT_TESTS
+
 # Does the user want to build the V2 API?
 if BUILD_V2
 V2_TEST = nctest
@@ -85,8 +90,8 @@ endif # BUILD_BENCHMARKS
 
 # Define Test directories
 if BUILD_TESTSETS
-TESTDIRS = $(V2_TEST) nc_test $(NC_TEST4) $(BENCHMARKS_DIR)	\
-$(HDF4_TEST_DIR) $(NCDAP2TESTDIR) $(NCDAP4TESTDIR)
+TESTDIRS = $(UNIT_TEST) $(V2_TEST) nc_test $(NC_TEST4)			\
+$(BENCHMARKS_DIR) $(HDF4_TEST_DIR) $(NCDAP2TESTDIR) $(NCDAP4TESTDIR)
 endif
 
 # This is the list of subdirs for which Makefiles will be constructed
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index ba1fc106e..04c7eb960 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -7,6 +7,13 @@ This file contains a high-level description of this package's evolution. Release
 
 ## 4.7.1 - TBD
 
+* [Bug Fix] Remove obsolete _CRAYMPP and LOCKNUMREC macros from
+code. Also brought documentation up to date in man page. These macros
+were used in ancient times, before modern parallel I/O systems were
+developed. Programmers interested in parallel I/O should see
+nc_open_par() and nc_create_par().
+See [GitHub #1436](https://github.com/Unidata/netcdf-c/issues/1459)
+
 * [Enhancement] Remove obsolete and deprecated functions
 nc_set_base_pe() and nc_inq_base_pe() from the dispatch table. (Both
 functions are still supported in the library, this is an internal
diff --git a/configure.ac b/configure.ac
index f311281ee..8ecd51400 100644
--- a/configure.ac
+++ b/configure.ac
@@ -184,6 +184,14 @@ if test "x$enable_jna" = xyes ; then
 AC_DEFINE([JNA], [1], [if true, include jna bug workaround code])
 fi
 
+# Does the user want to turn off unit tests (useful for test coverage
+# analysis).
+AC_ARG_ENABLE([unit-tests],
+  [AS_HELP_STRING([--disable-unit-tests],
+    [Disable tests in unit_test directory. Other tests still run.])])
+test "x$enable_unit_tests" = xno || enable_unit_tests=yes
+AM_CONDITIONAL([BUILD_UNIT_TESTS], [test "x$enable_unit_tests" = xyes])
+
 # Does the user want to build netcdf-4?
 AC_MSG_CHECKING([whether we should build netCDF-4])
 AC_ARG_ENABLE([netcdf-4], [AS_HELP_STRING([--disable-netcdf-4],
@@ -1536,6 +1544,7 @@ AC_CONFIG_FILES([Makefile
                  ncdump/expected/Makefile
                  docs/Makefile
                  docs/images/Makefile
+                 unit_test/Makefile
                  nctest/Makefile
                  nc_test4/Makefile
                  nc_perf/Makefile
diff --git a/docs/netcdf.m4 b/docs/netcdf.m4
index 19dd1d659..13b978928 100644
--- a/docs/netcdf.m4
+++ b/docs/netcdf.m4
@@ -1411,47 +1411,29 @@ May be used to change access to a variable from independent to collective data a
 .HP
 .SH "MPP FUNCTION DESCRIPTIONS"
 .LP
-Additional functions for use on SGI/Cray MPP machines (_CRAYMPP).
-These are used to set and inquire which PE is the base for MPP
-for a particular netCDF. These are only relevant when
-using the SGI/Cray ``global''
-Flexible File I/O layer and desire to have
-only a subset of PEs to open the specific netCDF file.
-For technical reasons, these functions are available on all platforms.
-On a platform other than SGI/Cray MPP, it is as if
-only processor available were processor 0.
+These functions were used on archaic SGI/Cray MPP machines. These
+functions are retained for backward compatibility; the PE arguments
+must all be set to zero.
 .LP
-To use this feature, you need to specify a communicator group and call
-CODE(glio_group_mpi(\|)) or CODE(glio_group_shmem(\|)) prior to the netCDF
-FREF(open) and FREF(create) calls.
 .HP
 FDECL(_create_mp, (IPATH(), ICMODE(), IINITSIZE(), IPE(), OCHUNKSIZE(), ONCID()))
 .sp
-Like FREF(_create) but allows the base PE to be set.
+Like FREF(_create).
 .sp
-The argument ARG(pe) sets the base PE at creation time. In the MPP
-environment, FREF(_create) and FREF(create) set the base PE to processor
-zero by default.
+The argument ARG(pe) must be zero.
 .HP
 FDECL(_open_mp, (IPATH(), IMODE(), IPE(), OCHUNKSIZE(), ONCID()))
 .sp
-Like FREF(_open) but allows the base PE to be set.
-The argument ARG(pe) sets the base PE at creation time. In the MPP
-environment, FREF(_open) and FREF(open) set the base PE to processor
-zero by default.
+Like FREF(_open).
+The argument ARG(pe) must be zero.
 .HP
 FDECL(inq_base_pe, (INCID(), OPE()))
 .sp
-Inquires of the netCDF dataset which PE is being used as the base for MPP use.
-This is safe to use at any time.
+Always returns pe of zero.
 .HP
 FDECL(set_base_pe, (INCID(), IPE()))
 .sp
-Resets the base PE for the netCDF dataset.
-Only perform this operation when the affected communicator group
-synchronizes before and after the call.
-This operation is very risky and should only be contemplated
-under only the most extreme cases.
+This function does nothing.
 .SH "ENVIRONMENT VARIABLES"
 .TP 4
 .B NETCDF_FFIOSPEC
diff --git a/include/nc3internal.h b/include/nc3internal.h
index a0e53bf62..af4f2ea72 100644
--- a/include/nc3internal.h
+++ b/include/nc3internal.h
@@ -221,15 +221,6 @@ NC_lookupvar(NC3_INFO* ncp, int varid, NC_var **varp);
 #define IS_RECVAR(vp)                                           \
     ((vp)->shape != NULL ? (*(vp)->shape == NC_UNLIMITED) : 0 )
 
-#ifdef LOCKNUMREC
-/*
- * typedef SHMEM type
- * for whenever the SHMEM functions can handle other than shorts
- */
-typedef unsigned short int      ushmem_t;
-typedef short int                shmem_t;
-#endif
-
 struct NC3_INFO {
     /* contains the previous NC during redef. */
     NC3_INFO *old;
@@ -258,18 +249,6 @@ struct NC3_INFO {
     NC_dimarray dims;
     NC_attrarray attrs;
     NC_vararray vars;
-#ifdef LOCKNUMREC
-/* size and named indexes for the lock array protecting NC.numrecs */
-#  define LOCKNUMREC_DIM        4
-#  define LOCKNUMREC_VALUE      0
-#  define LOCKNUMREC_LOCK       1
-#  define LOCKNUMREC_SERVING    2
-#  define LOCKNUMREC_BASEPE     3
-    /* Used on Cray T3E MPP to maintain the
-     * integrity of numrecs for an unlimited dimension
-     */
-    ushmem_t lock[LOCKNUMREC_DIM];
-#endif
 };
 
 #define NC_readonly(ncp)                        \
@@ -305,7 +284,6 @@ struct NC3_INFO {
 #define NC_doNsync(ncp)                         \
     fIsSet((ncp)->flags, NC_NSYNC)
 
-#ifndef LOCKNUMREC
 #  define NC_get_numrecs(nc3i)                  \
     ((nc3i)->numrecs)
 
@@ -314,11 +292,6 @@ struct NC3_INFO {
 
 #  define NC_increase_numrecs(nc3i, nrecs)                              \
     {if((nrecs) > (nc3i)->numrecs) ((nc3i)->numrecs = (nrecs));}
-#else
-    size_t NC_get_numrecs(const NC3_INFO *nc3i);
-void   NC_set_numrecs(NC3_INFO *nc3i, size_t nrecs);
-void   NC_increase_numrecs(NC3_INFO *nc3i, size_t nrecs);
-#endif
 
 /* Begin defined in nc.c */
 
diff --git a/include/netcdf.h b/include/netcdf.h
index 6ba8ba48b..c8b00395c 100644
--- a/include/netcdf.h
+++ b/include/netcdf.h
@@ -4,7 +4,7 @@ Main header file for the C API.
 
 Copyright 2018, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014,
-2015, 2016, 2017, 2018
+2015, 2016, 2017, 2018, 2019
 University Corporation for Atmospheric Research/Unidata.
 
 See \ref copyright file for more info.
@@ -1760,19 +1760,17 @@ nc_show_metadata(int ncid);
 
 /* End {put,get}_var */
 
-/* #ifdef _CRAYMPP */
-/*
- * Public interfaces to better support
- * CRAY multi-processor systems like T3E.
- * A tip of the hat to NERSC.
- */
+/* Delete a file. */
+EXTERNL int
+nc_delete(const char *path);
+
 /*
- * It turns out we need to declare and define
- * these public interfaces on all platforms
- * or things get ugly working out the
- * FORTRAN interface. On !_CRAYMPP platforms,
- * these functions work as advertised, but you
- * can only use "processor element" 0.
+ * The following functions were written to accomodate the old Cray
+ * systems. Modern HPC systems do not use these functions any more,
+ * but use the nc_open_par()/nc_create_par() functions instead. These
+ * functions are retained for backward compatibibility. These
+ * functions work as advertised, but you can only use "processor
+ * element" 0.
  */
 
 EXTERNL int
@@ -1783,9 +1781,6 @@ EXTERNL int
 nc__open_mp(const char *path, int mode, int basepe,
         size_t *chunksizehintp, int *ncidp);
 
-EXTERNL int
-nc_delete(const char *path);
-
 EXTERNL int
 nc_delete_mp(const char *path, int basepe);
 
@@ -1795,8 +1790,6 @@ nc_set_base_pe(int ncid, int pe);
 EXTERNL int
 nc_inq_base_pe(int ncid, int *pe);
 
-/* #endif _CRAYMPP */
-
 /* This v2 function is used in the nc_test program. */
 EXTERNL int
 nctypelen(nc_type datatype);
diff --git a/libdispatch/nc.c b/libdispatch/nc.c
index ba07c6538..f7b6d6750 100644
--- a/libdispatch/nc.c
+++ b/libdispatch/nc.c
@@ -1,31 +1,39 @@
 /*
- *	Copyright 2018, University Corporation for Atmospheric Research
+ *      Copyright 2018, University Corporation for Atmospheric Research
  *      See netcdf/COPYRIGHT file for copying and redistribution conditions.
  */
+/**
+ * @file
+ * @internal
+ *
+ * This file contains functions that work with the NC struct. There is
+ * an NC struct for every open netCDF file.
+ *
+ * @author Glenn Davis
+ */
 
 #include "config.h"
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
-#if defined(LOCKNUMREC) /* && _CRAYMPP */
-#  include <mpp/shmem.h>
-#  include <intrinsics.h>
-#endif
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
-
 #include "ncdispatch.h"
 
-/* This is the default create format for nc_create and nc__create. */
+/** This is the default create format for nc_create and nc__create. */
 static int default_create_format = NC_FORMAT_CLASSIC;
 
-/* These have to do with version numbers. */
-#define MAGIC_NUM_LEN 4
-#define VER_CLASSIC 1
-#define VER_64BIT_OFFSET 2
-#define VER_HDF5 3
-
+/**
+ * Find the NC struct for an open file, using the ncid.
+ *
+ * @param ncid The ncid to find.
+ * @param ncpp Pointer that gets a pointer to the NC.
+ *
+ * @return ::NC_NOERR No error.
+ * @return ::NC_EBADID ncid not found.
+ * @author Glenn Davis, Dennis Heimbigner
+ */
 int
 NC_check_id(int ncid, NC** ncpp)
 {
@@ -35,25 +43,45 @@ NC_check_id(int ncid, NC** ncpp)
     return NC_NOERR;
 }
 
+/**
+ * Free an NC struct and its related resources. Before this is done,
+ * be sure to remove the NC from the open file list with
+ * del_from_NCList().
+ *
+ * @param ncp Pointer to the NC struct to be freed.
+ *
+ * @author Glenn Davis, Dennis Heimbigner
+ */
 void
 free_NC(NC *ncp)
 {
     if(ncp == NULL)
-	return;
+        return;
     if(ncp->path)
-	free(ncp->path);
+        free(ncp->path);
     if(ncp->model)
-	free(ncp->model);
+        free(ncp->model);
     /* We assume caller has already cleaned up ncp->dispatchdata */
-#if _CRAYMPP && defined(LOCKNUMREC)
-    shfree(ncp);
-#else
     free(ncp);
-#endif /* _CRAYMPP && LOCKNUMREC */
 }
 
+/**
+ * Create and initialize a new NC struct. The ncid is assigned later.
+ *
+ * @param dispatcher
+ * @param path The name of the file.
+ * @param mode The open or create mode.
+ * @param model
+ * @param ncpp A pointer that gets a pointer to the newlly allocacted
+ * and initialized NC struct.
+ *
+ * @return ::NC_NOERR No error.
+ * @return ::NC_ENOMEM Out of memory.
+ * @author Glenn Davis, Dennis Heimbigner
+ */
 int
-new_NC(const NC_Dispatch* dispatcher, const char* path, int mode, NCmodel* model, NC** ncpp)
+new_NC(const NC_Dispatch* dispatcher, const char* path, int mode,
+       NCmodel* model, NC** ncpp)
 {
     NC *ncp = (NC*)calloc(1,sizeof(NC));
     if(ncp == NULL) return NC_ENOMEM;
@@ -61,31 +89,40 @@ new_NC(const NC_Dispatch* dispatcher, const char* path, int mode, NCmodel* model
     ncp->path = nulldup(path);
     ncp->mode = mode;
     if((ncp->model = malloc(sizeof(NCmodel)))==NULL)
-	return NC_ENOMEM;
+        return NC_ENOMEM;
     *ncp->model = *model; /* Make a copy */
     if(ncp->path == NULL) { /* fail */
         free_NC(ncp);
-	return NC_ENOMEM;
+        return NC_ENOMEM;
     }
     if(ncpp) {
-      *ncpp = ncp;
+        *ncpp = ncp;
     } else {
-      free_NC(ncp);
+        free_NC(ncp);
     }
     return NC_NOERR;
 }
 
-/* This function sets a default create flag that will be logically
-   or'd to whatever flags are passed into nc_create for all future
-   calls to nc_create.
-   Valid default create flags are NC_64BIT_OFFSET, NC_CLOBBER,
-   NC_LOCK, NC_SHARE. */
+/**
+ * This function sets a default create flag that will be logically
+ *  or'd to whatever flags are passed into nc_create for all future
+ *  calls to nc_create.
+ *
+ * @param format The format that should become the default.
+ * @param old_formatp Pointer that gets the previous default. Ignored
+ * if NULL.
+ *
+ * @return ::NC_NOERR No error.
+ * @return ::NC_ENOTBUILD Requested format not built with this install.
+ * @return ::NC_EINVAL Invalid input.
+ * @author Ed Hartnett, Dennis Heimbigner
+ */
 int
 nc_set_default_format(int format, int *old_formatp)
 {
     /* Return existing format if desired. */
     if (old_formatp)
-      *old_formatp = default_create_format;
+        *old_formatp = default_create_format;
 
     /* Make sure only valid format is set. */
 #ifndef ENABLE_CDF5
@@ -95,7 +132,7 @@ nc_set_default_format(int format, int *old_formatp)
 #ifdef USE_HDF5
     if (format != NC_FORMAT_CLASSIC && format != NC_FORMAT_64BIT_OFFSET &&
         format != NC_FORMAT_NETCDF4 && format != NC_FORMAT_NETCDF4_CLASSIC &&
-	format != NC_FORMAT_CDF5)
+        format != NC_FORMAT_CDF5)
         return NC_EINVAL;
 #else
     if (format == NC_FORMAT_NETCDF4 || format == NC_FORMAT_NETCDF4_CLASSIC)
@@ -108,6 +145,12 @@ nc_set_default_format(int format, int *old_formatp)
     return NC_NOERR;
 }
 
+/**
+ * Get the current default format.
+ *
+ * @return the default format.
+ * @author Ed Hartnett
+ */
 int
 nc_get_default_format(void)
 {
diff --git a/libdispatch/nclistmgr.c b/libdispatch/nclistmgr.c
index 043addb6e..e05b7a3a4 100644
--- a/libdispatch/nclistmgr.c
+++ b/libdispatch/nclistmgr.c
@@ -1,7 +1,15 @@
 /*********************************************************************
    Copyright 2018, UCAR/Unidata See netcdf/COPYRIGHT file for
    copying and redistribution conditions.
- *********************************************************************/
+*********************************************************************/
+/**
+ * @file
+ *
+ * Functions to manage the list of NC structs. There is one NC struct
+ * for each open file.
+ *
+ * @author Dennis Heimbigner
+*/
 
 #include "config.h"
 #include <stdlib.h>
@@ -9,22 +17,40 @@
 #include <assert.h>
 #include "ncdispatch.h"
 
+/** This shift is applied to the ext_ncid in order to get the index in
+ * the array of NC. */
 #define ID_SHIFT (16)
+
+/** This is the length of the NC list - the number of files that can
+ * be open at one time. We use 2^16 = 65536 entries in the array, but
+ * slot 0 is not used, so only 65535 files may be open at one
+ * time. */
 #define NCFILELISTLENGTH 0x10000
 
-/* Version one just allocates the max space (sizeof(NC*)*2^16)*/
+/** This is the pointer to the array of NC, one for each open file. */
 static NC** nc_filelist = NULL;
 
+/** The number of files currently open. */
 static int numfiles = 0;
 
-/* Common */
+/**
+ * How many files are currently open?
+ *
+ * @return number of open files.
+ * @author Dennis Heimbigner
+ */
 int
 count_NCList(void)
 {
     return numfiles;
 }
 
-
+/**
+ * Free an empty NCList. @note If list is not empty, or has not been
+ * allocated, function will silently exit.
+ *
+ * @author Dennis Heimbigner
+ */
 void
 free_NCList(void)
 {
@@ -33,86 +59,156 @@ free_NCList(void)
     nc_filelist = NULL;
 }
 
+/**
+ * Add an already-allocated NC to the list. It will be assigned an
+ * ncid in this function.
+ *
+ * If this is the first file to be opened, the nc_filelist will be
+ * allocated and set to all 0.
+ *
+ * The ncid is assigned by finding the first open index in the
+ * nc_filelist array (skipping index 0). The ncid is this index
+ * left-shifted ID_SHIFT bits (16). This puts the file ID in the first
+ * two bytes of the 4-byte integer, and leaves the last two bytes for
+ * group IDs for netCDF-4 files.
+ *
+ * @param ncp Pointer to already-allocated and initialized NC struct.
+ *
+ * @return ::NC_NOERR No error.
+ * @return ::NC_ENOMEM Out of memory.
+ * @author Dennis Heimbigner
+ */
 int
 add_to_NCList(NC* ncp)
 {
     int i;
     int new_id;
     if(nc_filelist == NULL) {
-	if (!(nc_filelist = calloc(1, sizeof(NC*)*NCFILELISTLENGTH)))
-	    return NC_ENOMEM;
-	numfiles = 0;
+        if (!(nc_filelist = calloc(1, sizeof(NC*)*NCFILELISTLENGTH)))
+            return NC_ENOMEM;
+        numfiles = 0;
     }
 
     new_id = 0; /* id's begin at 1 */
     for(i=1; i < NCFILELISTLENGTH; i++) {
-	if(nc_filelist[i] == NULL) {new_id = i; break;}
+        if(nc_filelist[i] == NULL) {new_id = i; break;}
     }
     if(new_id == 0) return NC_ENOMEM; /* no more slots */
     nc_filelist[new_id] = ncp;
     numfiles++;
-    new_id = (new_id << ID_SHIFT);
-    ncp->ext_ncid = new_id;
+    ncp->ext_ncid = (new_id << ID_SHIFT);
     return NC_NOERR;
 }
 
+/**
+ * Delete an NC struct from the list. This happens when the file is
+ * closed. Relies on all memory in the NC being deallocated after this
+ * function with freeNC().
+ *
+ * @note If the file list is empty, or this NC can't be found in the
+ * list, this function will silently exit.
+ *
+ * @param ncp Pointer to NC to be removed from list.
+ *
+ * @author Dennis Heimbigner
+ */
 void
 del_from_NCList(NC* ncp)
 {
-   unsigned int ncid = ((unsigned int)ncp->ext_ncid) >> ID_SHIFT;
-   if(numfiles == 0 || ncid == 0 || nc_filelist == NULL) return;
-   if(nc_filelist[ncid] != ncp) return;
+    unsigned int ncid = ((unsigned int)ncp->ext_ncid) >> ID_SHIFT;
+    if(numfiles == 0 || ncid == 0 || nc_filelist == NULL) return;
+    if(nc_filelist[ncid] != ncp) return;
 
-   nc_filelist[ncid] = NULL;
-   numfiles--;
+    nc_filelist[ncid] = NULL;
+    numfiles--;
 
-   /* If all files have been closed, release the filelist memory. */
-   if (numfiles == 0)
-      free_NCList();
+    /* If all files have been closed, release the filelist memory. */
+    if (numfiles == 0)
+        free_NCList();
 }
 
+/**
+ * Find an NC in the list, given an ext_ncid. The NC list is indexed
+ * with the first two bytes of ext_ncid. (The last two bytes specify
+ * the group for netCDF4 files, or are zeros for classic files.)
+ *
+ * @param ext_ncid The ncid of the file to find.
+ *
+ * @return pointer to NC or NULL if not found.
+ * @author Dennis Heimbigner, Ed Hartnett
+ */
 NC *
 find_in_NCList(int ext_ncid)
 {
-   NC* f = NULL;
-   unsigned int ncid = ((unsigned int)ext_ncid) >> ID_SHIFT;
-   if(numfiles > 0 && nc_filelist != NULL && ncid < NCFILELISTLENGTH)
-	f = nc_filelist[ncid];
+    NC* f = NULL;
+    /* Discard the first two bytes of ext_ncid to get ncid. */
+    unsigned int ncid = ((unsigned int)ext_ncid) >> ID_SHIFT;
+
+    /* If we have a filelist, there will be an entry, possibly NULL,
+     * for this ncid. */
+    if (nc_filelist)
+    {
+        assert(numfiles);
+        f = nc_filelist[ncid];
+    }
 
-   /* for classic files, ext_ncid must be a multiple of (1<<ID_SHIFT) */
-   if (f != NULL && f->model->impl == NC_FORMATX_NC3 && (ext_ncid % (1<<ID_SHIFT)))
-       return NULL;
+    /* For classic files, ext_ncid must be a multiple of
+     * (1<<ID_SHIFT). That is, the group part of the ext_ncid (the
+     * last two bytes) must be zero. If not, then return NULL, which
+     * will eventually lead to an NC_EBADID error being returned to
+     * user. */
+    if (f != NULL && f->model->impl == NC_FORMATX_NC3 && (ext_ncid % (1<<ID_SHIFT)))
+        return NULL;
 
-   return f;
+    return f;
 }
 
-/*
-Added to support open by name
-*/
+/**
+ * Find an NC in the list using the file name.
+ *
+ * @param path Name of the file.
+ *
+ * @return pointer to NC or NULL if not found.
+ * @author Dennis Heimbigner
+ */
 NC*
 find_in_NCList_by_name(const char* path)
 {
-   int i;
-   NC* f = NULL;
-   if(nc_filelist == NULL)
-	return NULL;
-   for(i=1; i < NCFILELISTLENGTH; i++) {
-	if(nc_filelist[i] != NULL) {
-	    if(strcmp(nc_filelist[i]->path,path)==0) {
-		f = nc_filelist[i];
-		break;
-	    }
-	}
-   }
-   return f;
+    int i;
+    NC* f = NULL;
+    if(nc_filelist == NULL)
+        return NULL;
+    for(i=1; i < NCFILELISTLENGTH; i++) {
+        if(nc_filelist[i] != NULL) {
+            if(strcmp(nc_filelist[i]->path,path)==0) {
+                f = nc_filelist[i];
+                break;
+            }
+        }
+    }
+    return f;
 }
 
+/**
+ * Find an NC in list based on its index. The index is ((unsigned
+ * int)ext_ncid) >> ID_SHIFT. This is the two high bytes of the
+ * ext_ncid. (The other two bytes are used for the group ID for
+ * netCDF-4 files.)
+ *
+ * @param index The index in the NC list.
+ * @param ncp Pointer that gets pointer to the next NC. Igored if
+ * NULL.
+ *
+ * @return ::NC_NOERR No error.
+ * @return ::NC_ERANGE Index out of range.
+ * @author Dennis Heimbigner
+ */
 int
 iterate_NCList(int index, NC** ncp)
 {
     /* Walk from 0 ...; 0 return => stop */
     if(index < 0 || index >= NCFILELISTLENGTH)
-	return NC_ERANGE;
+        return NC_ERANGE;
     if(ncp) *ncp = nc_filelist[index];
     return NC_NOERR;
 }
diff --git a/libsrc/nc3internal.c b/libsrc/nc3internal.c
index 063f6864e..36afe710d 100644
--- a/libsrc/nc3internal.c
+++ b/libsrc/nc3internal.c
@@ -10,10 +10,6 @@
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
-#if defined(LOCKNUMREC) /* && _CRAYMPP */
-#  include <mpp/shmem.h>
-#  include <intrinsics.h>
-#endif
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
@@ -47,11 +43,7 @@ free_NC3INFO(NC3_INFO *nc3)
 	free_NC_dimarrayV(&nc3->dims);
 	free_NC_attrarrayV(&nc3->attrs);
 	free_NC_vararrayV(&nc3->vars);
-#if _CRAYMPP && defined(LOCKNUMREC)
-	shfree(nc3);
-#else
 	free(nc3);
-#endif /* _CRAYMPP && LOCKNUMREC */
 }
 
 static NC3_INFO *
@@ -948,21 +940,6 @@ NC_endef(NC3_INFO *ncp,
 	return ncio_sync(ncp->nciop);
 }
 
-#ifdef LOCKNUMREC
-static int
-NC_init_pe(NC *ncp, int basepe) {
-	if (basepe < 0 || basepe >= _num_pes()) {
-		return NC_EINVAL; /* invalid base pe */
-	}
-	/* initialize common values */
-	ncp->lock[LOCKNUMREC_VALUE] = 0;
-	ncp->lock[LOCKNUMREC_LOCK] = 0;
-	ncp->lock[LOCKNUMREC_SERVING] = 0;
-	ncp->lock[LOCKNUMREC_BASEPE] =  basepe;
-	return NC_NOERR;
-}
-#endif
-
 
 /*
  * Compute the expected size of the file.
@@ -1017,11 +994,7 @@ int NC3_new_nc(NC3_INFO** ncpp)
 	NC *nc;
 	NC3_INFO* nc3;
 
-#if _CRAYMPP && defined(LOCKNUMREC)
-	ncp = (NC *) shmalloc(sizeof(NC));
-#else
 	ncp = (NC *) malloc(sizeof(NC));
-#endif /* _CRAYMPP && LOCKNUMREC */
 	if(ncp == NULL)
 		return NC_ENOMEM;
 	(void) memset(ncp, 0, sizeof(NC));
@@ -1058,20 +1031,13 @@ NC3_create(const char *path, int ioflags, size_t initialsz, int basepe,
 	fSet(ioflags, NC_SHARE);
 #endif
 
-#if defined(LOCKNUMREC) /* && _CRAYMPP */
-	if (status = NC_init_pe(nc3, basepe)) {
-		return status;
-	}
-#else
 	/*
-	 * !_CRAYMPP, only pe 0 is valid
+	 * Only pe 0 is valid
 	 */
 	if(basepe != 0) {
-      if(nc3) free(nc3);
-      return NC_EINVAL;
-    }
-#endif
-
+            if(nc3) free(nc3);
+            return NC_EINVAL;
+        }
 	assert(nc3->flags == 0);
 
 	/* Now we can set min size */
@@ -1193,23 +1159,17 @@ NC3_open(const char *path, int ioflags, int basepe, size_t *chunksizehintp,
 	fSet(ioflags, NC_SHARE);
 #endif
 
-#if defined(LOCKNUMREC) /* && _CRAYMPP */
-	if (status = NC_init_pe(nc3, basepe)) {
-	    goto unwind_alloc;
-	}
-#else
 	/*
-	 * !_CRAYMPP, only pe 0 is valid
+	 * Only pe 0 is valid.
 	 */
 	if(basepe != 0) {
-      if(nc3) {
-        free(nc3);
-        nc3 = NULL;
-      }
-      status = NC_EINVAL;
-      goto unwind_alloc;
-    }
-#endif
+            if(nc3) {
+                free(nc3);
+                nc3 = NULL;
+            }
+            status = NC_EINVAL;
+            goto unwind_alloc;
+        }
 
 #ifdef ENABLE_BYTERANGE
     /* If the model specified the use of byte-ranges, then signal by
@@ -1577,37 +1537,6 @@ NC3_set_fill(int ncid,
 	return NC_NOERR;
 }
 
-#ifdef LOCKNUMREC
-
-/* create function versions of the NC_*_numrecs macros */
-size_t
-NC_get_numrecs(const NC *nc3) {
-	shmem_t numrec;
-	shmem_short_get(&numrec, (shmem_t *) nc3->lock + LOCKNUMREC_VALUE, 1,
-		nc3->lock[LOCKNUMREC_BASEPE]);
-	return (size_t) numrec;
-}
-
-void
-NC_set_numrecs(NC *nc3, size_t nrecs)
-{
-    shmem_t numrec = (shmem_t) nrecs;
-    /* update local value too */
-    nc3->lock[LOCKNUMREC_VALUE] = (ushmem_t) numrec;
-    shmem_short_put((shmem_t *) nc3->lock + LOCKNUMREC_VALUE, &numrec, 1,
-    nc3->lock[LOCKNUMREC_BASEPE]);
-}
-
-void NC_increase_numrecs(NC *nc3, size_t nrecs)
-{
-    /* this is only called in one place that's already protected
-     * by a lock ... so don't worry about it */
-    if (nrecs > NC_get_numrecs(nc3))
-	NC_set_numrecs(nc3, nrecs);
-}
-
-#endif /* LOCKNUMREC */
-
 /**
  * Return the file format.
  *
@@ -1719,7 +1648,18 @@ NC3_inq_type(int ncid, nc_type typeid, char *name, size_t *size)
    return NC_NOERR;
 }
 
-/**************************************************/
+/**
+ * This is an obsolete form of nc_delete(), supported for backwards
+ * compatibility.
+ *
+ * @param path Filename to delete.
+ * @param basepe Must be 0.
+ *
+ * @return ::NC_NOERR No error.
+ * @return ::NC_EIO Couldn't delete file.
+ * @return ::NC_EINVAL Invaliod basepe. Must be 0.
+ * @author Glenn Davis, Ed Hartnett
+ */
 int
 nc_delete_mp(const char * path, int basepe)
 {
@@ -1733,17 +1673,11 @@ nc_delete_mp(const char * path, int basepe)
 	status = NC_check_id(ncid,&nc);
         if(status) return status;
 
-#if defined(LOCKNUMREC) /* && _CRAYMPP */
-	if (status = NC_init_pe(nc3, basepe)) {
-		return status;
-	}
-#else
 	/*
-	 * !_CRAYMPP, only pe 0 is valid
+	 * Only pe 0 is valid.
 	 */
 	if(basepe != 0)
 		return NC_EINVAL;
-#endif
 
 	(void) nc_close(ncid);
 	if(unlink(path) == -1) {
diff --git a/libsrc/putget.m4 b/libsrc/putget.m4
index 644a7dd8f..ebd0fc04a 100644
--- a/libsrc/putget.m4
+++ b/libsrc/putget.m4
@@ -28,13 +28,6 @@ dnl
 #include "ncx.h"
 #include "fbits.h"
 #include "onstack.h"
-#ifdef LOCKNUMREC
-#  include <mpp/shmem.h>	/* for SGI/Cray SHMEM routines */
-#  ifdef LN_TEST
-#    include <stdio.h>
-#  endif
-#endif
-
 
 #undef MIN  /* system may define MIN somewhere and complain */
 #define MIN(mm,nn) (((mm) < (nn)) ? (mm) : (nn))
@@ -403,40 +396,6 @@ static int
 NCvnrecs(NC3_INFO* ncp, size_t numrecs)
 {
 	int status = NC_NOERR;
-#ifdef LOCKNUMREC
-	ushmem_t myticket = 0, nowserving = 0;
-	ushmem_t numpe = (ushmem_t) _num_pes();
-
-	/* get ticket and wait */
-	myticket = shmem_short_finc((shmem_t *) ncp->lock + LOCKNUMREC_LOCK,
-		ncp->lock[LOCKNUMREC_BASEPE]);
-#ifdef LN_TEST
-		fprintf(stderr,"%d of %d : ticket = %hu\n",
-			_my_pe(), _num_pes(), myticket);
-#endif
-	do {
-		shmem_short_get((shmem_t *) &nowserving,
-			(shmem_t *) ncp->lock + LOCKNUMREC_SERVING, 1,
-			ncp->lock[LOCKNUMREC_BASEPE]);
-#ifdef LN_TEST
-		fprintf(stderr,"%d of %d : serving = %hu\n",
-			_my_pe(), _num_pes(), nowserving);
-#endif
-		/* work-around for non-unique tickets */
-		if (nowserving > myticket && nowserving < myticket + numpe ) {
-			/* get a new ticket ... you've been bypassed */
-			/* and handle the unlikely wrap-around effect */
-			myticket = shmem_short_finc(
-				(shmem_t *) ncp->lock + LOCKNUMREC_LOCK,
-				ncp->lock[LOCKNUMREC_BASEPE]);
-#ifdef LN_TEST
-				fprintf(stderr,"%d of %d : new ticket = %hu\n",
-					_my_pe(), _num_pes(), myticket);
-#endif
-		}
-	} while(nowserving != myticket);
-	/* now our turn to check & update value */
-#endif
 
 	if(numrecs > NC_get_numrecs(ncp))
 	{
@@ -519,11 +478,6 @@ NCvnrecs(NC3_INFO* ncp, size_t numrecs)
 
 	}
 common_return:
-#ifdef LOCKNUMREC
-	/* finished with our lock - increment serving number */
-	(void) shmem_short_finc((shmem_t *) ncp->lock + LOCKNUMREC_SERVING,
-		ncp->lock[LOCKNUMREC_BASEPE]);
-#endif
 	return status;
 }
 
diff --git a/ncgen/main.c b/ncgen/main.c
index 51524db20..178729ce7 100644
--- a/ncgen/main.c
+++ b/ncgen/main.c
@@ -244,11 +244,6 @@ main(
 #endif
     memset(&globalspecials,0,sizeof(GlobalSpecialData));
 
-#if _CRAYMPP && 0
-    /* initialize CRAY MPP parallel-I/O library */
-    (void) par_io_init(32, 32);
-#endif
-
     while ((c = getopt(argc, argv, "134567bB:cdD:fhHk:l:M:no:Pv:xL:N:")) != EOF)
       switch(c) {
 	case 'd':
diff --git a/ncgen3/main.c b/ncgen3/main.c
index 0d861ad7a..830508885 100644
--- a/ncgen3/main.c
+++ b/ncgen3/main.c
@@ -114,11 +114,6 @@ main(
     cmode_modifier = 0;
     nofill_flag = 0;
 
-#if _CRAYMPP && 0
-    /* initialize CRAY MPP parallel-I/O library */
-    (void) par_io_init(32, 32);
-#endif
-
     while ((c = getopt(argc, argv, "bcfk:l:no:v:x")) != EOF)
       switch(c) {
 	case 'c':		/* for c output, old version of "-lc" */


Test patch:
diff --git a/unit_test/Makefile.am b/unit_test/Makefile.am
new file mode 100644
index 000000000..c83024a77
--- /dev/null
+++ b/unit_test/Makefile.am
@@ -0,0 +1,21 @@
+## This is a automake file, part of Unidata's netCDF package.
+# Copyright 2019, see the COPYRIGHT file for more information.
+
+# This file builds and runs the unit tests. These tests are not run in
+# the CMake build, because we would then have to extern these internal
+# functions, to allow Windows to work. Since we have not extern'd
+# these functions, they will only be run under the autotools build.
+
+# Ed Hartnett 8/9/19
+
+# Put together AM_CPPFLAGS and AM_LDFLAGS.
+include $(top_srcdir)/lib_flags.am
+
+# Find and link to the netcdf-c library.
+LDADD = ${top_builddir}/liblib/libnetcdf.la
+
+check_PROGRAMS = tst_nclist
+TESTS = tst_nclist
+
+# If valgrind is present, add valgrind targets.
+@VALGRIND_CHECK_RULES@
diff --git a/unit_test/tst_nclist.c b/unit_test/tst_nclist.c
new file mode 100644
index 000000000..3637a1642
--- /dev/null
+++ b/unit_test/tst_nclist.c
@@ -0,0 +1,110 @@
+/* This is part of the netCDF package. Copyright 2005-2019 University
+   Corporation for Atmospheric Research/Unidata. See COPYRIGHT file
+   for conditions of use.
+
+   Test list functions in nclistmgr.c.
+
+   Ed Hartnett, 8/10/19
+*/
+
+#include "config.h"
+#include <nc_tests.h>
+#include "nc.h"
+#include "ncdispatch.h"
+#include "err_macros.h"
+
+/* An integer value to use in testing. */
+#define TEST_VAL_42 42
+
+int
+main(int argc, char **argv)
+{
+    printf("\n*** Testing netcdf internal NC list functions.\n");
+    printf("Testing NC list functions with no open files...");
+    {
+        /* These are cases where there are no files, or NULL
+         * params. */
+        if (count_NCList()) ERR;
+        free_NCList();
+        if (find_in_NCList_by_name("nope")) ERR;
+        if (iterate_NCList(0, NULL)) ERR;
+    }
+    SUMMARIZE_ERR;
+    printf("Testing adding to NC list...");
+    {
+        int ncid;
+        NC *ncp, *ncp2;
+        int mode = 0;
+        NCmodel model;
+        char path[] = {"file.nc"};
+        int ret;
+
+        /* Create the NC* instance and insert its dispatcher and model. */
+        if ((ret = new_NC(NULL, path, mode, &model, &ncp))) ERR;
+
+        /* Nothing to find yet. */
+        if (find_in_NCList(TEST_VAL_42)) ERR;
+
+        /* Add to list of known open files and define ext_ncid. */
+        add_to_NCList(ncp);
+
+        /* These won't work! */
+        if (find_in_NCList(TEST_VAL_42)) ERR;
+        if (find_in_NCList_by_name("nope")) ERR;
+        if ((ret = iterate_NCList(2, &ncp2))) ERR;
+
+        /* Find it in the list. */
+        if (!(ncp2 = find_in_NCList(ncp->ext_ncid))) ERR;
+        if (!(ncp2 = find_in_NCList_by_name(path))) ERR;
+        if ((ret = iterate_NCList(1, &ncp2))) ERR;
+        if (count_NCList() != 1) ERR;
+
+        /* Won't do anything because list contains an entry. */
+        free_NCList();
+
+        /* Delete it. */
+        ncid = ncp->ext_ncid;
+        del_from_NCList(ncp); /* Will free empty list. */
+        free_NC(ncp);
+        if (find_in_NCList(ncid)) ERR;
+    }
+    SUMMARIZE_ERR;
+#ifdef LARGE_FILE_TESTS
+    /* This test is slow, only run it on large file test builds. */
+    printf("Testing maxing out NC list...");
+    {
+        NC *ncp;
+        int mode = 0;
+        NCmodel model;
+        char path[] = {"file.nc"};
+        int max_num_nc = 65535;
+        int i;
+        int ret;
+
+        /* Fill the NC list. */
+        for (i = 0; i < max_num_nc; i++)
+        {
+            if ((ret = new_NC(NULL, path, mode, &model, &ncp))) ERR;
+            if (add_to_NCList(ncp)) ERR;
+        }
+
+        /* Check the count. */
+        if (count_NCList() != max_num_nc) ERR;
+
+        /* Try and add another. It will fail. */
+        if (add_to_NCList(ncp) != NC_ENOMEM) ERR;
+
+        /* Delete them all. */
+        for (i = 0; i < max_num_nc; i++)
+        {
+            if (iterate_NCList(i + 1, &ncp)) ERR;
+            if (!ncp) ERR;
+            del_from_NCList(ncp);
+            free_NC(ncp);
+        }
+    }
+    SUMMARIZE_ERR;
+#endif /* LARGE_FILE_TESTS */
+
+    FINAL_RESULTS;
+}