Repository: bazelbuild/bazel
Issue: Duplicate entries in `/proc/meminfo` make bazel server crash
Labels: more data needed
untriaged
team-Local-Exec


### Description of the problem / feature request:

running `bazel build //:hello_world` leads to

```
$ bazel build //:hello_world
Starting local Bazel server and connecting to it...

Server terminated abruptly (error code: 14, error message: 'Socket closed', log file: '/home/dark/.cache/bazel/_bazel_dark/37eeb5c8b6e4daaae7f4d5edfdef7735/server/jvm.out')
```

jvm.out:
```
java.lang.IllegalArgumentException: Multiple entries with same key: Writeback=0 and Writeback=0
        at com.google.common.collect.ImmutableMap.conflictException(ImmutableMap.java:215)
        at com.google.common.collect.ImmutableMap.checkNoConflict(ImmutableMap.java:209)
        at com.google.common.collect.RegularImmutableMap.checkNoConflictInKeyBucket(RegularImmutableMap.java:147)
        at com.google.common.collect.RegularImmutableMap.fromEntryArray(RegularImmutableMap.java:110)
        at com.google.common.collect.ImmutableMap$Builder.build(ImmutableMap.java:393)
        at com.google.devtools.build.lib.unix.ProcMeminfoParser.<init>(ProcMeminfoParser.java:62)
        at com.google.devtools.build.lib.actions.LocalHostResourceManagerLinux.getMemoryInMbHelper(LocalHostResourceManagerLinux.java:86)
        at com.google.devtools.build.lib.actions.LocalHostResourceManagerLinux.getMemoryInMb(LocalHostResourceManagerLinux.java:40)
        at com.google.devtools.build.lib.actions.LocalHostResourceManagerLinux.getLocalHostResources(LocalHostResourceManagerLinux.java:46)
        at com.google.devtools.build.lib.actions.LocalHostCapacity.getNewLocalHostCapacity(LocalHostCapacity.java:46)
        at com.google.devtools.build.lib.actions.LocalHostCapacity.getLocalHostCapacity(LocalHostCapacity.java:34)
        at com.google.devtools.build.lib.runtime.LoadingPhaseThreadsOption$LoadingPhaseThreadCountConverter.lambda$new$0(LoadingPhaseThreadsOption.java:54)
        at com.google.devtools.build.lib.util.ResourceConverter.applyOperator(ResourceConverter.java:136)
        at com.google.devtools.build.lib.util.ResourceConverter.convert(ResourceConverter.java:122)
        at com.google.devtools.build.lib.util.ResourceConverter.convert(ResourceConverter.java:41)
        at com.google.devtools.common.options.OptionDefinition.getDefaultValue(OptionDefinition.java:270)
        at com.google.devtools.common.options.OptionValueDescription$DefaultOptionValueDescription.getValue(OptionValueDescription.java:123)
        at com.google.devtools.common.options.OptionsParserImpl.parse(OptionsParserImpl.java:378)
        at com.google.devtools.common.options.OptionsParserImpl.parse(OptionsParserImpl.java:325)
        at com.google.devtools.common.options.OptionsParser.parseWithSourceFunction(OptionsParser.java:642)
        at com.google.devtools.build.lib.runtime.BlazeOptionHandler.parseArgsAndConfigs(BlazeOptionHandler.java:207)
        at com.google.devtools.build.lib.runtime.BlazeOptionHandler.parseOptions(BlazeOptionHandler.java:274)
        at com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.execExclusively(BlazeCommandDispatcher.java:275)
        at com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.exec(BlazeCommandDispatcher.java:208)
        at com.google.devtools.build.lib.server.GrpcServerImpl.executeCommand(GrpcServerImpl.java:603)
        at com.google.devtools.build.lib.server.GrpcServerImpl.lambda$run$2(GrpcServerImpl.java:659)
        at io.grpc.Context$1.run(Context.java:595)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.base/java.lang.Thread.run(Unknown Source)
ERROR: bazel crash in async thread:
java.lang.IllegalArgumentException: Multiple entries with same key: Writeback=0 and Writeback=0
        at com.google.common.collect.ImmutableMap.conflictException(ImmutableMap.java:215)
        at com.google.common.collect.ImmutableMap.checkNoConflict(ImmutableMap.java:209)
        at com.google.common.collect.RegularImmutableMap.checkNoConflictInKeyBucket(RegularImmutableMap.java:147)
        at com.google.common.collect.RegularImmutableMap.fromEntryArray(RegularImmutableMap.java:110)
        at com.google.common.collect.ImmutableMap$Builder.build(ImmutableMap.java:393)
        at com.google.devtools.build.lib.unix.ProcMeminfoParser.<init>(ProcMeminfoParser.java:62)
        at com.google.devtools.build.lib.actions.LocalHostResourceManagerLinux.getMemoryInMbHelper(LocalHostResourceManagerLinux.java:86)
        at com.google.devtools.build.lib.actions.LocalHostResourceManagerLinux.getMemoryInMb(LocalHostResourceManagerLinux.java:40)
        at com.google.devtools.build.lib.actions.LocalHostResourceManagerLinux.getLocalHostResources(LocalHostResourceManagerLinux.java:46)
        at com.google.devtools.build.lib.actions.LocalHostCapacity.getNewLocalHostCapacity(LocalHostCapacity.java:46)
        at com.google.devtools.build.lib.actions.LocalHostCapacity.getLocalHostCapacity(LocalHostCapacity.java:34)
        at com.google.devtools.build.lib.runtime.LoadingPhaseThreadsOption$LoadingPhaseThreadCountConverter.lambda$new$0(LoadingPhaseThreadsOption.java:54)
        at com.google.devtools.build.lib.util.ResourceConverter.applyOperator(ResourceConverter.java:136)
        at com.google.devtools.build.lib.util.ResourceConverter.convert(ResourceConverter.java:122)
        at com.google.devtools.build.lib.util.ResourceConverter.convert(ResourceConverter.java:41)
        at com.google.devtools.common.options.OptionDefinition.getDefaultValue(OptionDefinition.java:270)
        at com.google.devtools.common.options.OptionValueDescription$DefaultOptionValueDescription.getValue(OptionValueDescription.java:123)
        at com.google.devtools.common.options.OptionsParserImpl.parse(OptionsParserImpl.java:378)
        at com.google.devtools.common.options.OptionsParserImpl.parse(OptionsParserImpl.java:325)
        at com.google.devtools.common.options.OptionsParser.parseWithSourceFunction(OptionsParser.java:642)
        at com.google.devtools.build.lib.runtime.BlazeOptionHandler.parseArgsAndConfigs(BlazeOptionHandler.java:207)
        at com.google.devtools.build.lib.runtime.BlazeOptionHandler.parseOptions(BlazeOptionHandler.java:274)
        at com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.execExclusively(BlazeCommandDispatcher.java:275)
        at com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.exec(BlazeCommandDispatcher.java:208)
        at com.google.devtools.build.lib.server.GrpcServerImpl.executeCommand(GrpcServerImpl.java:603)
        at com.google.devtools.build.lib.server.GrpcServerImpl.lambda$run$2(GrpcServerImpl.java:659)
        at io.grpc.Context$1.run(Context.java:595)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.base/java.lang.Thread.run(Unknown Source)
```

### Bugs: what's the simplest, easiest way to reproduce this bug? Please provide a minimal example if possible.

> Replace this line with your answer.

### What operating system are you running Bazel on?

```
$ uname -a
Linux buildbox 5.3.13-2-pve #1 SMP PVE 5.3.13-2 (Fri, 24 Jan 2020 09:49:36 +0100) x86_64 x86_64 x86_64 GNU/Linux
```

/proc/meminfo

```
MemTotal:       100663296 kB
MemFree:        93340676 kB
MemAvailable:   98430428 kB
Buffers:               0 kB
Cached:          5089752 kB
SwapCached:            0 kB
Active:          4217336 kB
Inactive:        1956280 kB
Active(anon):    1565476 kB
Inactive(anon):   745800 kB
Active(file):    2651860 kB
Inactive(file):  1210480 kB
Unevictable:           0 kB
Mlocked:            5336 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:                 0 kB
Writeback:             0 kB
AnonPages:        733396 kB
Mapped:            43032 kB
Shmem:           1577880 kB
KReclaimable:    1557508 kB
Slab:               0 kB
SReclaimable:          0 kB
SUnreclaim:            0 kB
KernelStack:       41392 kB
PageTables:       103680 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
Writeback:             0 kB
CommitLimit:    65929152 kB
Committed_AS:   19702672 kB
VmallocTotal:   34359738367 kB
VmallocUsed:     3950392 kB
VmallocChunk:          0 kB
Percpu:           564736 kB
HardwareCorrupted:     0 kB
AnonHugePages:         0 kB
ShmemHugePages:        0 kB
ShmemPmdMapped:        0 kB
CmaTotal:              0 kB
CmaFree:               0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB
DirectMap4k:    94660008 kB
DirectMap2M:    37351424 kB
DirectMap1G:     2097152 kB
```

### What's the output of `bazel info release`?
```
$ bazel info release
Starting local Bazel server and connecting to it...

Server terminated abruptly (error code: 14, error message: 'Socket closed', log file: '/home/dark/.cache/bazel/_bazel_dark/37eeb5c8b6e4daaae7f4d5edfdef7735/server/jvm.out')
```

### If `bazel info release` returns "development version" or "(@non-git)", tell us how you built Bazel.

this is official release of 2.2.0

```
$ bazel version
Starting local Bazel server and connecting to it...
Build label: 2.2.0
Build target: bazel-out/k8-opt/bin/src/main/java/com/google/devtools/build/lib/bazel/BazelServer_deploy.jar
Build time: Tue Mar 3 09:26:12 2020 (1583227572)
Build timestamp: 1583227572
Build timestamp as int: 1583227572
```

### What's the output of `git remote get-url origin ; git rev-parse master ; git rev-parse HEAD` ?
This isn't a git repo.


###  Have you found anything relevant by searching the web?
No, but I've found tests
https://github.com/bazelbuild/bazel/blob/1f684e1b87cd8881a0a4b33e86ba66743e32d674/src/test/java/com/google/devtools/build/lib/actions/LocalHostResourceManagerLinuxTest.java

that seem to not cover this case

### Any other information, logs, or outputs that you want to share?
See above

Issue comments:
There simply is not enough to triage this.
How did you build the container that causes this?
Is this actually an error in container setup, or a normal use case?
Hi,
looks like I am running in to the same problem.

I have a proxmox server and created a LXC Debain 10 container in it.



```
[lxc]~/bazel_examples/examples/cpp-tutorial/stage1 (master) $ bazel build --verbose_failures //main:hello-world 
Starting local Bazel server and connecting to it...

Server terminated abruptly (error code: 14, error message: 'Socket closed', log file: '/home/sfos/.cache/bazel/_bazel_sfos/3f321be1cafa1793bc6eb1acb8b9c579/server/jvm.out')

[lxc]~/bazel_examples/examples/cpp-tutorial/stage1 (master) $ cat /home/sfos/.cache/bazel/_bazel_sfos/3f321be1cafa1793bc6eb1acb8b9c579/server/jvm.out
java.lang.IllegalArgumentException: Multiple entries with same key: Writeback=1056 and Writeback=1056
	at com.google.common.collect.ImmutableMap.conflictException(ImmutableMap.java:215)
	at com.google.common.collect.ImmutableMap.checkNoConflict(ImmutableMap.java:209)
	at com.google.common.collect.RegularImmutableMap.checkNoConflictInKeyBucket(RegularImmutableMap.java:147)
	at com.google.common.collect.RegularImmutableMap.fromEntryArray(RegularImmutableMap.java:110)
	at com.google.common.collect.ImmutableMap$Builder.build(ImmutableMap.java:393)
	at com.google.devtools.build.lib.unix.ProcMeminfoParser.<init>(ProcMeminfoParser.java:62)
	at com.google.devtools.build.lib.actions.LocalHostResourceManagerLinux.getMemoryInMbHelper(LocalHostResourceManagerLinux.java:86)
	at com.google.devtools.build.lib.actions.LocalHostResourceManagerLinux.getMemoryInMb(LocalHostResourceManagerLinux.java:40)
	at com.google.devtools.build.lib.actions.LocalHostResourceManagerLinux.getLocalHostResources(LocalHostResourceManagerLinux.java:46)
	at com.google.devtools.build.lib.actions.LocalHostCapacity.getNewLocalHostCapacity(LocalHostCapacity.java:46)
	at com.google.devtools.build.lib.actions.LocalHostCapacity.getLocalHostCapacity(LocalHostCapacity.java:34)
	at com.google.devtools.build.lib.runtime.LoadingPhaseThreadsOption$LoadingPhaseThreadCountConverter.lambda$new$0(LoadingPhaseThreadsOption.java:54)
	at com.google.devtools.build.lib.util.ResourceConverter.applyOperator(ResourceConverter.java:136)
	at com.google.devtools.build.lib.util.ResourceConverter.convert(ResourceConverter.java:122)
	at com.google.devtools.build.lib.util.ResourceConverter.convert(ResourceConverter.java:41)
	at com.google.devtools.common.options.OptionDefinition.getDefaultValue(OptionDefinition.java:271)
	at com.google.devtools.common.options.OptionValueDescription$DefaultOptionValueDescription.getValue(OptionValueDescription.java:123)
	at com.google.devtools.common.options.OptionsParserImpl.parse(OptionsParserImpl.java:378)
	at com.google.devtools.common.options.OptionsParserImpl.parse(OptionsParserImpl.java:325)
	at com.google.devtools.common.options.OptionsParser.parseWithSourceFunction(OptionsParser.java:653)
	at com.google.devtools.build.lib.runtime.BlazeOptionHandler.parseArgsAndConfigs(BlazeOptionHandler.java:200)
	at com.google.devtools.build.lib.runtime.BlazeOptionHandler.parseOptions(BlazeOptionHandler.java:267)
	at com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.execExclusively(BlazeCommandDispatcher.java:275)
	at com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.exec(BlazeCommandDispatcher.java:208)
	at com.google.devtools.build.lib.server.GrpcServerImpl.executeCommand(GrpcServerImpl.java:603)
	at com.google.devtools.build.lib.server.GrpcServerImpl.lambda$run$2(GrpcServerImpl.java:663)
	at io.grpc.Context$1.run(Context.java:595)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.base/java.lang.Thread.run(Unknown Source)
ERROR: bazel crash in async thread:
java.lang.IllegalArgumentException: Multiple entries with same key: Writeback=1056 and Writeback=1056
	at com.google.common.collect.ImmutableMap.conflictException(ImmutableMap.java:215)
	at com.google.common.collect.ImmutableMap.checkNoConflict(ImmutableMap.java:209)
	at com.google.common.collect.RegularImmutableMap.checkNoConflictInKeyBucket(RegularImmutableMap.java:147)
	at com.google.common.collect.RegularImmutableMap.fromEntryArray(RegularImmutableMap.java:110)
	at com.google.common.collect.ImmutableMap$Builder.build(ImmutableMap.java:393)
	at com.google.devtools.build.lib.unix.ProcMeminfoParser.<init>(ProcMeminfoParser.java:62)
	at com.google.devtools.build.lib.actions.LocalHostResourceManagerLinux.getMemoryInMbHelper(LocalHostResourceManagerLinux.java:86)
	at com.google.devtools.build.lib.actions.LocalHostResourceManagerLinux.getMemoryInMb(LocalHostResourceManagerLinux.java:40)
	at com.google.devtools.build.lib.actions.LocalHostResourceManagerLinux.getLocalHostResources(LocalHostResourceManagerLinux.java:46)
	at com.google.devtools.build.lib.actions.LocalHostCapacity.getNewLocalHostCapacity(LocalHostCapacity.java:46)
	at com.google.devtools.build.lib.actions.LocalHostCapacity.getLocalHostCapacity(LocalHostCapacity.java:34)
	at com.google.devtools.build.lib.runtime.LoadingPhaseThreadsOption$LoadingPhaseThreadCountConverter.lambda$new$0(LoadingPhaseThreadsOption.java:54)
	at com.google.devtools.build.lib.util.ResourceConverter.applyOperator(ResourceConverter.java:136)
	at com.google.devtools.build.lib.util.ResourceConverter.convert(ResourceConverter.java:122)
	at com.google.devtools.build.lib.util.ResourceConverter.convert(ResourceConverter.java:41)
	at com.google.devtools.common.options.OptionDefinition.getDefaultValue(OptionDefinition.java:271)
	at com.google.devtools.common.options.OptionValueDescription$DefaultOptionValueDescription.getValue(OptionValueDescription.java:123)
	at com.google.devtools.common.options.OptionsParserImpl.parse(OptionsParserImpl.java:378)
	at com.google.devtools.common.options.OptionsParserImpl.parse(OptionsParserImpl.java:325)
	at com.google.devtools.common.options.OptionsParser.parseWithSourceFunction(OptionsParser.java:653)
	at com.google.devtools.build.lib.runtime.BlazeOptionHandler.parseArgsAndConfigs(BlazeOptionHandler.java:200)
	at com.google.devtools.build.lib.runtime.BlazeOptionHandler.parseOptions(BlazeOptionHandler.java:267)
	at com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.execExclusively(BlazeCommandDispatcher.java:275)
	at com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.exec(BlazeCommandDispatcher.java:208)
	at com.google.devtools.build.lib.server.GrpcServerImpl.executeCommand(GrpcServerImpl.java:603)
	at com.google.devtools.build.lib.server.GrpcServerImpl.lambda$run$2(GrpcServerImpl.java:663)
	at io.grpc.Context$1.run(Context.java:595)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.base/java.lang.Thread.run(Unknown Source)
```

```
$ bazel version
Starting local Bazel server and connecting to it...
Build label: 3.1.0
Build target: bazel-out/k8-opt/bin/src/main/java/com/google/devtools/build/lib/bazel/BazelServer_deploy.jar
Build time: Wed Apr 22 10:32:27 2020 (1587551547)
Build timestamp: 1587551547
Build timestamp as int: 1587551547
```

```
# cat /proc/meminfo | grep Writeback
Writeback:          1056 kB
Writeback:          1056 kB
```
This should be easy to fix. Do you want to send a patch?
from reading the code the fix is most likely:

```diff
diff --git a/src/main/java/com/google/devtools/build/lib/unix/ProcMeminfoParser.java b/src/main/java/com/google/devtools/build/lib/unix/ProcMeminfoParser.java
index dcfe8086f8..c3c15367c8 100644
--- a/src/main/java/com/google/devtools/build/lib/unix/ProcMeminfoParser.java
+++ b/src/main/java/com/google/devtools/build/lib/unix/ProcMeminfoParser.java
@@ -54,7 +54,9 @@ public class ProcMeminfoParser {
       String valString = line.substring(colon + 1);
       try {
         long val =  Long.parseLong(CharMatcher.inRange('0', '9').retainFrom(valString));
-        builder.put(keyword, val);
+        if (!builder.containsKey(keyword)) {
+          builder.put(keyword, val);
+        }
       } catch (NumberFormatException e) {
         // Ignore: we'll fail later if somebody tries to capture this value.
       }

```

but need to build it for testing, 
which is not possible on this machine do to the bug.

Update:

short update, the `ImmutableMap.Builder` does not have a `containsKey` only `ImmutableMap`.
a bit more code restructure is needed, will open a PR when I get it working.
Should be fixed by ae03671e73e0a1933f0514019e33e357b1fb6050 unless I'm mistaken.

Pull request review comments:
Just change this into `memInfo = ImmutableMap.copyOf(newMemInfo);`, and drop the builder variable above. No need to create three maps if two are enough.
The code implements first wins, but this asserts last wins. Did you actually run the test?
good point, changed
No, since I where confused by the documentation, 
what are the values I need to use for the `...`?
in this command ```bazel test //src/... //third_party/ijar/...```
Can you add documentation here what happens if there are duplicate entries?
`bazel test //src/test/java/com/google/devtools/build/lib:unix_test` is probably good enough for local testing in this case. :-)

updated

Patch that resolved the issue:
diff --git a/src/main/java/com/google/devtools/build/lib/unix/ProcMeminfoParser.java b/src/main/java/com/google/devtools/build/lib/unix/ProcMeminfoParser.java
index dcfe8086f8eb68..92dd93cc2d6a77 100644
--- a/src/main/java/com/google/devtools/build/lib/unix/ProcMeminfoParser.java
+++ b/src/main/java/com/google/devtools/build/lib/unix/ProcMeminfoParser.java
@@ -23,9 +23,12 @@
 import java.nio.charset.Charset;
 import java.util.List;
 import java.util.Map;
+import java.util.HashMap;
 
 /**
  * Parse and return information from /proc/meminfo.
+ * In case of duplicate entries the first one is used
+ * and other values are skipped.
  */
 public class ProcMeminfoParser {
 
@@ -44,7 +47,7 @@ public ProcMeminfoParser() throws IOException {
   @VisibleForTesting
   public ProcMeminfoParser(String fileName) throws IOException {
     List<String> lines = Files.readLines(new File(fileName), Charset.defaultCharset());
-    ImmutableMap.Builder<String, Long> builder = ImmutableMap.builder();
+    Map<String, Long> newMemInfo = new HashMap<>();
     for (String line : lines) {
       int colon = line.indexOf(':');
       if (colon == -1) {
@@ -54,12 +57,14 @@ public ProcMeminfoParser(String fileName) throws IOException {
       String valString = line.substring(colon + 1);
       try {
         long val =  Long.parseLong(CharMatcher.inRange('0', '9').retainFrom(valString));
-        builder.put(keyword, val);
+        if (!newMemInfo.containsKey(keyword)) {
+          newMemInfo.put(keyword, val);
+        }
       } catch (NumberFormatException e) {
         // Ignore: we'll fail later if somebody tries to capture this value.
       }
     }
-    memInfo = builder.build();
+    memInfo = ImmutableMap.copyOf(newMemInfo);
   }
 
   /** Gets a named field in KB. */


Test patch:
diff --git a/src/test/java/com/google/devtools/build/lib/unix/ProcMeminfoParserTest.java b/src/test/java/com/google/devtools/build/lib/unix/ProcMeminfoParserTest.java
index 059c58ae24fdc8..fff62cddfafdae 100644
--- a/src/test/java/com/google/devtools/build/lib/unix/ProcMeminfoParserTest.java
+++ b/src/test/java/com/google/devtools/build/lib/unix/ProcMeminfoParserTest.java
@@ -66,6 +66,7 @@ public void memInfo() throws Exception {
         "Hugepagesize:     2048 kB",
         "Bogus: not_a_number",
         "Bogus2: 1000000000000000000000000000000000000000000000000 kB",
+        "Writeback:         123 kB",
         "Not even a valid line"
     );
 
@@ -75,6 +76,7 @@ public void memInfo() throws Exception {
     assertThat(memInfo.getFreeRamKb()).isEqualTo(14717640);
     assertThat(memInfo.getRamKb("Cached")).isEqualTo(509940);
     assertThat(memInfo.getTotalKb()).isEqualTo(3091732);
+    assertThat(memInfo.getRamKb("Writeback")).isEqualTo(0);
     assertThrows(ProcMeminfoParser.KeywordNotFoundException.class,
         () -> memInfo.getRamKb("Bogus"));
     assertThrows(ProcMeminfoParser.KeywordNotFoundException.class,