Dotnet-docker: String.ToLower() in 3.0-alpine image ignores cyrillic chars

Created on 1 Oct 2019  路  6Comments  路  Source: dotnet/dotnet-docker

Steps to reproduce the issue

  1. Create basic console app.
  2. Create a string with uppercase cyrillic characters.
  3. Create another string from the first string by calling String.ToLower().
  4. Output this new string to console.
  5. Pack this console app into docker image using mcr.microsoft.com/dotnet/core/aspnet:3.0-alpine as a base.
  6. Run the built image.
  7. Observe in output that uppercase cyrillic characters were not affected by String.ToLower().

Expected behavior

Uppercase cyrillic characters should become lowercase after a call to String.ToLower().

Actual behavior

Uppercase cyrillic characters remain uppercase after a call to String.ToLower().

Additional information (e.g. issue happens only occasionally)

Works fine in the Debian-based mcr.microsoft.com/dotnet/core/aspnet:3.0 image.

See the attached project for repro:

ToLowerTest.zip

Initial string: 袩袪袠袙袝孝 PRIVET. Container output when running docker-compose up:

to-lower-test-alpine_1  | 袩袪袠袙袝孝 privet
to-lower-test-debian_1  | 锌褉懈胁械褌 privet

Output of docker version

Docker version 19.03.2, build 6a30dfc

Output of docker info

Client:
 Debug Mode: false

Server:
 Containers: 13
  Running: 2
  Paused: 0
  Stopped: 11
 Images: 106
 Server Version: 19.03.2
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
 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: 894b81a4b802e4eb2a91d1ce216b8817763c29fb
 runc version: 425e105d5a03fabd737a126ad93d62a9eeede87f
 init version: fec3683
 Security Options:
  seccomp
   Profile: default
 Kernel Version: 4.9.184-linuxkit
 Operating System: Docker Desktop
 OSType: linux
 Architecture: x86_64
 CPUs: 2
 Total Memory: 1.952GiB
 Name: docker-desktop
 ID: HXTF:N7FS:QCDZ:VLFL:HX7O:4JZJ:XFZG:7ZYN:6YIF:WAK3:3SON:HQXU
 Docker Root Dir: /var/lib/docker
 Debug Mode: true
  File Descriptors: 40
  Goroutines: 58
  System Time: 2019-10-01T19:38:33.2742731Z
  EventsListeners: 2
 HTTP Proxy: gateway.docker.internal:3128
 HTTPS Proxy: gateway.docker.internal:3129
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false
 Product License: Community Engine

Most helpful comment

Sounds like you are running into the globalization invariant mode:

String casing (ToUpper and ToLower) will be performed for the ASCII range only. Requests to case code points outside that range will not be performed, however no exception will be thrown. In other words, casing will only be performed for character range ['a'..'z'].

The original alpine announcement talks about invariant mode too:

We have been considering various design decisions to make .NET Core Alpine base images as small as possible to align with that. In this first iteration, we enabled .NET Core 2.0 Globalization Invariant Mode in order to reduce the default size of the image. This change reduced the image by ~30MB.

https://github.com/dotnet/dotnet-docker/issues/986#issuecomment-472467432 describes how to disable it. I think the short version is to do this in your own dockerfile:

ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
RUN apk add --no-cache icu-libs

All 6 comments

Sounds like you are running into the globalization invariant mode:

String casing (ToUpper and ToLower) will be performed for the ASCII range only. Requests to case code points outside that range will not be performed, however no exception will be thrown. In other words, casing will only be performed for character range ['a'..'z'].

The original alpine announcement talks about invariant mode too:

We have been considering various design decisions to make .NET Core Alpine base images as small as possible to align with that. In this first iteration, we enabled .NET Core 2.0 Globalization Invariant Mode in order to reduce the default size of the image. This change reduced the image by ~30MB.

https://github.com/dotnet/dotnet-docker/issues/986#issuecomment-472467432 describes how to disable it. I think the short version is to do this in your own dockerfile:

ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
RUN apk add --no-cache icu-libs

Here's a complete dockerfile that shows how to enable full globalization mode: https://github.com/dotnet/dotnet-docker/blob/master/samples/dotnetapp/Dockerfile.alpine-x64-globalization

Thanks @omajid, that seems to do the job:

ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
RUN apk add --no-cache icu-libs

Is it okay though that this behavior changed from 2.2-alpine to 3.0-alpine?

Oh, sorry, I didn't use globalization related code prior to 3.0-alpine, so I guess it has always been this way (since 2.0-alpine).

Mystery solved.

FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-alpine
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
RUN apk add --no-cache icu-libs

gives on build

Sending build context to Docker daemon 45.19MB Step 1/3 : FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-alpine ---> 3628001d3a2c Step 2/3 : ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false ---> Running in a8edbaffd678 Removing intermediate container a8edbaffd678 ---> 2cf2c20a4f95 Step 3/3 : RUN apk add --no-cache icu-libs ---> Running in df2135d80a3f fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz WARNING: Ignoring http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz: No such file or directory WARNING: Ignoring http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz: No such file or directory ERROR: unsatisfiable constraints: icu-libs (missing): required by: world[icu-libs] The command '/bin/sh -c apk add --no-cache icu-libs' returned a non-zero code: 1

@SquallHalle Did you solve your issue? I get the same error when using the aspnetcore 2.2-alpine images:
mcr.microsoft.com/dotnet/core/aspnet:2.2-alpine
mcr.microsoft.com/dotnet/core/aspnet:2.2-alpine3.9

Was this page helpful?
0 / 5 - 0 ratings