[MLton] Writing memory to disk ...

Stephen Weeks MLton@mlton.org
Wed, 24 May 2006 23:14:46 -0700


I refactored the runtime's disk-backing code so it can be implemented
in the platform/* files.  The idea was to add three functions

  void diskBack_close (void *data);
  void diskBack_read (void *data, pointer buf, size_t size);
  void *diskBack_write (pointer buf, size_t size);

With these, the disk-backing code in gc/heap.c looks as follows.

    /* Write the heap to disk and try again. */
    void *data;

    data = diskBack_write (orig, size);
    releaseHeap (s, curHeapp);
    if (createHeap (s, curHeapp, desiredSize, minSize)) {
      diskBack_read (data, curHeapp->start, size);
      diskBack_close (data);
    } else {
      diskBack_close (data);
      if (s->controls.messages)
        GC_displayMem ();
      die ("Out of memory.  Unable to allocate %s bytes.\n",
           uintmaxToCommaString(minSize));
    }

On Unix platforms, disckBack_* use mkstemp + unlink, while on Windows,
they use GetTemp{FileName,Path} + FILE_FLAG_DELETE_ON_CLOSE as
suggested by Adam.  On Unix, the default temp dir is /var/tmp, and can
be overridden by environment variable TMP or TMPDIR.

All that remains is testing and adding the runtime control option.  I
haven't thought of anything better than disk-backed, so will go with
that in the absence of other suggestions.  (heap-on-disk?,
heap-to-disk?)


Here's the Unix implementation of the diskBack_* functions.

--------------------------------------------------------------------------------
static int tempFileDes (void) {
  int fd;
  char *template;
  const char *tmpDir;
  const char *tag = "/TempFileXXXXXXXXXX";
  mode_t m;

  tmpDir = getenv ("TMP");
  if (NULL == tmpDir) {
    tmpDir = getenv ("TMPDIR");
    if (NULL == tmpDir)
      tmpDir = "/var/tmp";
  }
  template = malloc_safe (strlen(tmpDir) + strlen(tag) + 1);
  strcpy (template, tmpDir);
  strcat (template, tag);
  m = umask(077);
  fd = mkstemp_safe (template);
  (void)umask(m);
  unlink_safe (template);
  free (template);
  return fd;
}

typedef struct {
  int fd;
} *WriteToDiskData;

void diskBack_read (void *data, pointer buf, size_t size) {
  int fd;

  fd = ((WriteToDiskData)data)->fd;
  lseek (fd, 0, SEEK_SET);
  read_safe (fd, buf, size);
}

void diskBack_close (void *data) {
  int fd;

  fd = ((WriteToDiskData)data)->fd;
  close_safe (fd);
  free (data);
}

void *diskBack_write (pointer buf, size_t size) {
  int fd;
  WriteToDiskData d;

  fd = tempFileDes ();
  write_safe (fd, buf, size);
  d = (WriteToDiskData)(malloc_safe (sizeof(*d)));
  d->fd = fd;
  return d;
}
--------------------------------------------------------------------------------

Here's the Windows implementation of the diskBack_* functions.

--------------------------------------------------------------------------------
#define BUFSIZE 65536

static HANDLE tempFileDes (void) {
  /* Based on http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/creating_and_using_a_temporary_file.asp
   */  
  HANDLE hTempFile; 
  DWORD dwRetVal;
  DWORD dwBufSize=BUFSIZE;
  UINT uRetVal;
  char szTempName[BUFSIZE];  
  char lpPathBuffer[BUFSIZE];

  dwRetVal = GetTempPath(dwBufSize, lpPathBuffer);
  if (dwRetVal > dwBufSize)
    die ("GetTempPath failed with error %ld\n", GetLastError());
  uRetVal = GetTempFileName(lpPathBuffer, "TempFile", 0, szTempName);
  if (0 == uRetVal)
    die ("GetTempFileName failed with error %ld\n", GetLastError());
  hTempFile = CreateFile((LPTSTR) szTempName, GENERIC_READ | GENERIC_WRITE,
    0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
    NULL);                
  if (hTempFile == INVALID_HANDLE_VALUE)
    die ("CreateFile failed with error %ld\n", GetLastError());
  return hTempFile;
}

typedef struct {
  HANDLE handle;
} *WriteToDiskData;

void diskBack_read (void *data, pointer buf, size_t size) {
  HANDLE h;
  DWORD d;
  DWORD dwBytesRead;

  h = ((WriteToDiskData)data)->handle;
  d = SetFilePointer (h, 0, NULL, FILE_BEGIN);
  if (d == INVALID_SET_FILE_POINTER)
    die ("SetFilePointer failed with error %ld\n", GetLastError());
  unless (ReadFile(h, buf, size, &dwBytesRead, NULL))
    die ("ReadFile failed with error %ld\n", GetLastError());
}

void diskBack_close (void *data) {
  HANDLE h;

  h = ((WriteToDiskData)data)->handle;
  unless (CloseHandle (h))
    die ("CloseHandle failed with error %ld.", GetLastError());
  free (data);
}

void *diskBack_write (pointer buf, size_t size) {
  HANDLE h;
  WriteToDiskData d;
  DWORD dwBytesWritten;

  h = tempFileDes ();
  unless (WriteFile (h, buf, size, &dwBytesWritten, NULL))
    die ("WriteFile failed with error %ld\n", GetLastError());
  d = (WriteToDiskData)(malloc_safe (sizeof(*d)));
  d->handle = h;
  return d;
}