Skip to content

Very specific set of circumstances leads to zero-byte (empty) file being created #1015

Closed
@pedantic-git

Description

@pedantic-git
  • This is a bug report
  • This is a feature request
  • I searched existing issues before opening this one

The story

A few weeks ago, a bunch of tests in Rails code I have started failing in my Docker development environment. My dev environment is Arch so the actual set of libraries and things installed is very much a moving target.

Lots of digging around led me to create a repo that can reproduce the issue using only Ruby (no gems at all), at the lowest level possible. And it's really really weird. More information below.

Expected behavior

When copying a file in Ruby using the IO.copy_stream method, the file should be an identical copy.

Actual behavior

Under a very specific set of circumstances, the resulting file is 0 bytes.

I'm reasonably certain this is a Docker issue, because one of the conditions that needs to be true is that the source file is located on a mounted volume.

The conditions that need to be true for the IO.copy_stream operation to fail are:

  1. The source file is located on a Docker mounted volume and not the Docker container's own filesystem.
  2. The file is first copied into a Tempfile using the low-level Tempfile library.
  3. The tempfile is not touched (e.g. by utime or chmod) after the copy happens.
  4. That tempfile is then copied to another location using IO.copy_stream. Copying it using File.write and tempfile.read does not cause the issue.

Steps to reproduce the behavior

Please check out this repo: https://github.com/fishpercolator/reproduce-copystream and run the command in the README.

The output of this command on my env is:

open, local, File.write: 6744
open, local, IO.copy_stream: 6744

open, mounted, File.write: 6744
open, mounted, IO.copy_stream: 6744

tempfile + copy_file, local, File.write: 6744
tempfile + copy_file, local, IO.copy_stream: 6744

tempfile + copy_file, mounted, File.write: 6744
tempfile + copy_file, mounted, IO.copy_stream: 0

tempfile + copy_file + noop utime, mounted, File.write: 6744
tempfile + copy_file + noop utime, mounted, IO.copy_stream: 6744

tempfile + copy_file + noop chmod, mounted, File.write: 6744
tempfile + copy_file + noop chmod, mounted, IO.copy_stream: 6744

As you can see, the file is copied successfully if it is:

  1. copied directly from the mounted filesystem (not via a tempfile)
  2. copied via a tempfile from the container's own filesystem
  3. copied via a tempfile that has been touched with utime or chmod after the contents were inserted
  4. I do this same operation on an Ubuntu machine running Docker 19.03.9 ... i.e. it seems to be specific to something about my development machine

If any of the 4 statements above are true, the file copies fine. If all 4 are false, the file is created but it has a 0-byte size.

I've checked to see if the source filesystem of the mounted volume makes any difference but I get the same effect with eCryptfs, ext4 and tmpfs mountpoints.

Likewise I've tried different versions of Ruby to see if it was a regression in Ruby, but I can still reproduce the issue with the official Docker images of much older versions where I was certain it worked.

Does anyone have any ideas? Can anyone else reproduce this using my repo?

Output of docker version:

Client:
 Version:           19.03.9-ce
 API version:       1.40
 Go version:        go1.14.3
 Git commit:        9d988398e7
 Built:             Tue May 19 22:11:18 2020
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          19.03.9-ce
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.14.3
  Git commit:       9d988398e7
  Built:            Tue May 19 22:11:01 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.3.4.m
  GitCommit:        d76c121f76a5fc8a462dc64594aea72fe18e1178.m
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

Output of docker info:

Client:
 Debug Mode: false

Server:
 Containers: 317
  Running: 0
  Paused: 0
  Stopped: 317
 Images: 415
 Server Version: 19.03.9-ce
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: false
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: d76c121f76a5fc8a462dc64594aea72fe18e1178.m
 runc version: dc9208a3303feef5b3839f4323d9beb36df0a9dd
 init version: fec3683
 Security Options:
  seccomp
   Profile: default
 Kernel Version: 5.6.13-arch1-1
 Operating System: Arch Linux
 OSType: linux
 Architecture: x86_64
 CPUs: 4
 Total Memory: 15.53GiB
 Name: hulat
 ID: G7VK:VZOA:T4LI:ATNZ:G55B:TDEA:23J2:YM2A:MOLK:U5QY:5MVT:CU7P
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Username: pedanticgit
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false

Additional environment details (AWS, VirtualBox, physical, etc.)

It's a physical Dell XPS-13 9360 running Arch Linux. Arch doesn't have version numbers but all packages are up to date. Docker was installed from the Arch package with is built using this PKGBUILD script from the official Docker sources.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions