Repository: huggingface/dataset-viewer

Issue:
Finish the post-messages script earlier
The post-messages have been running for more than 15 hours now even if it does not take more than 2 minutes to complete.

https://cd.internal.huggingface.tech/applications/argo-cd/datasets-server-prod?view=pods&node=%2FPod%2Fdatasets-server%2Fprod-datasets-server-job-post-messages-28412650-w4x4c%2F0

<img width="897" alt="Capture d’écran 2024-01-09 à 15 42 33" src="https://github.com/huggingface/datasets-server/assets/1676121/85a02b83-dccd-4b8a-8fa9-d7ac381242fe">



Discussion:
Today, we only have a very old pod (prod-datasets-server-job-post-messages-28421290-j96c9, since 15th of January)

We only see these logs in ArgoCD:

```
INFO: 2024-01-15 00:10:04,787 - root - Create a Hub discussion to notify about parquet conversion
INFO: 2024-01-15 00:10:04,906 - root - Creating discussions for 248 datasets
```

But the other pods created since have finished quickly. So: I delete this pod manually, and we can check later if the issue appears again.
```
$ k get jobs
NAME                                                        COMPLETIONS   DURATION   AGE
prod-datasets-server-job-clean-hf-datasets-cache-28426320   0/1           79m        79m
prod-datasets-server-job-post-messages-28421290             0/1           3d13h      3d13h
prod-datasets-server-job-queue-metrics-collector-28426370   1/1           9s         29m
prod-datasets-server-job-queue-metrics-collector-28426380   1/1           9s         19m
prod-datasets-server-job-queue-metrics-collector-28426390   1/1           9s         9m26s
$ k delete jobs/prod-datasets-server-job-post-messages-28421290
job.batch "prod-datasets-server-job-post-messages-28421290" deleted
```
cron jobs from the three last days are still not finished

<img width="449" alt="Capture d’écran 2024-01-22 à 10 56 06" src="https://github.com/huggingface/datasets-server/assets/1676121/44375363-51d7-467c-9799-738a430e07c0">

From the logs:

Run from Jan 19, 2024 @ 01:10:04.280000000: did not finish
Run from Jan 20, 2024 @ 01:10:04.290000000: finished, `INFO: 2024-01-20 00:14:22,634 - root - Duration: 0:04:18.375422`
Run from Jan 21, 2024 @ 01:10:04.290000000: did not finish
Run from Jan 22, 2024 @ 01:10:04.760000000: did not finish

For the last one, even if it has started 10 hours ago, we only have two logs!
```
INFO: 2024-01-22 00:10:04,670 - root - Create a Hub discussion to notify about parquet conversion
INFO: 2024-01-22 00:10:04,760 - root - Creating discussions for 219 datasets
```

Resolving patch:
diff --git a/jobs/cache_maintenance/src/cache_maintenance/discussions.py b/jobs/cache_maintenance/src/cache_maintenance/discussions.py
--- a/jobs/cache_maintenance/src/cache_maintenance/discussions.py
+++ b/jobs/cache_maintenance/src/cache_maintenance/discussions.py
@@ -2,8 +2,8 @@
 # Copyright 2023 The HuggingFace Authors.
 
 import logging
-from time import sleep
-from typing import Literal, Optional, TypedDict
+from dataclasses import dataclass
+from typing import Literal, Optional
 from urllib import parse
 
 from huggingface_hub import HfApi
@@ -14,14 +14,16 @@
 DAYS = 1
 
 
-class ParquetCounters(TypedDict):
-    datasets: int
-    new_discussions: int
-    dismissed_discussions: int
-    errors: int
+@dataclass
+class ParquetCounters:
+    datasets: int = 0
+    new_discussions: int = 0
+    dismissed_discussions: int = 0
+    errors: int = 0
 
 
-class Counters(TypedDict):
+@dataclass
+class Counters:
     parquet: ParquetCounters
 
 
@@ -57,37 +59,32 @@ def post_messages_on_parquet_conversion(
 
     logging.info(f"Creating discussions for {len(datasets)} datasets")
     log_batch = 100
-    counters: ParquetCounters = {
-        "datasets": 0,
-        "new_discussions": 0,
-        "dismissed_discussions": 0,
-        "errors": 0,
-    }
+    counters = ParquetCounters()
 
     def get_log() -> str:
         return (
-            f" {counters['new_discussions']} discussions have been opened. A total of"
-            f" {len(datasets)} datasets were selected, but {counters['dismissed_discussions']} datasets"
-            f" already had a discussion (open or closed). {counters['errors']} errors."
+            f" [{counters.datasets}/{len(datasets)}] {counters.new_discussions} discussions"
+            f" have been opened, {counters.dismissed_discussions} datasets"
+            f" already had a discussion (open or closed). {counters.errors} errors."
         )
 
     hf_api = HfApi(endpoint=hf_endpoint, token=bot_token)
 
     for dataset in datasets:
-        counters["datasets"] += 1
+        counters.datasets += 1
+        prefix = f"[{counters.datasets}/{len(datasets)}]"
+        logging.info(f"{prefix} Processing dataset {dataset}")
         try:
-            bot_discussions = [
-                discussion
-                for discussion in hf_api.get_repo_discussions(
-                    repo_id=dataset, repo_type=REPO_TYPE_DATASET, token=bot_token
+            try:
+                next(
+                    hf_api.get_repo_discussions(
+                        repo_id=dataset, repo_type=REPO_TYPE_DATASET, token=bot_token, author=bot_associated_user_name
+                    )
                 )
-                if discussion.author == bot_associated_user_name
-            ]
-
-            if bot_discussions:
-                counters["dismissed_discussions"] += 1
-                continue
-            else:
+                # if we get here, the bot has already opened a discussion for this dataset
+                counters.dismissed_discussions += 1
+                logging.info(f"{prefix} [dismissed] Dataset {dataset} already has a discussion, skipping")
+            except StopIteration:
                 hf_api.create_discussion(
                     repo_id=dataset,
                     repo_type=REPO_TYPE_DATASET,
@@ -100,16 +97,14 @@ def get_log() -> str:
                     ),
                     token=bot_token,
                 )
-                sleep(1)
-                # ^ see https://github.com/huggingface/moon-landing/issues/7729 (internal)
-                counters["new_discussions"] += 1
-
+                counters.new_discussions += 1
+                logging.info(f"{prefix} [new] Dataset {dataset} has a new discussion")
         except Exception as e:
-            logging.warning(f"Failed to post a message for {dataset}: {e}")
-            counters["errors"] += 1
+            counters.errors += 1
+            logging.warning(f"{prefix} [error] Failed to process dataset {dataset}: {e}")
 
         logging.debug(get_log())
-        if (counters["datasets"]) % log_batch == 0:
+        if (counters.datasets) % log_batch == 0:
             logging.info(get_log())
 
     logging.info(get_log())


Test patch:
diff --git a/jobs/cache_maintenance/tests/test_discussions.py b/jobs/cache_maintenance/tests/test_discussions.py
--- a/jobs/cache_maintenance/tests/test_discussions.py
+++ b/jobs/cache_maintenance/tests/test_discussions.py
@@ -12,6 +12,7 @@
 from cache_maintenance.discussions import (
     DAYS,
     PARQUET_CACHE_KIND,
+    ParquetCounters,
     create_discussion_description,
     create_link,
     limit_to_one_dataset_per_namespace,
@@ -62,6 +63,7 @@ def test_create_link() -> None:
     )
 
 
+@pytest.mark.skip("the user name is invalid, see #8742 in moonlanding")
 def test_post_messages_in_one_dataset(job_config: JobConfig) -> None:
     with TemporaryDataset(prefix="dataset") as dataset:
         assert fetch_bot_discussion(dataset=dataset.repo_id) is None
@@ -82,12 +84,7 @@ def test_post_messages_in_one_dataset(job_config: JobConfig) -> None:
             parquet_revision=job_config.discussions.parquet_revision,
         )
         # ensure one discussion has been created
-        assert counters["parquet"] == {
-            "datasets": 1,
-            "new_discussions": 1,
-            "dismissed_discussions": 0,
-            "errors": 0,
-        }
+        assert counters.parquet == ParquetCounters(datasets=1, new_discussions=1)
         first_discussion = fetch_bot_discussion(dataset=dataset.repo_id)
         assert first_discussion is not None
         assert count_comments(first_discussion) == 1
@@ -116,12 +113,7 @@ def test_post_messages_in_one_dataset(job_config: JobConfig) -> None:
             parquet_revision=job_config.discussions.parquet_revision,
         )
         # ensure no new discussion has been created
-        assert counters["parquet"] == {
-            "datasets": 1,
-            "new_discussions": 0,
-            "dismissed_discussions": 1,
-            "errors": 0,
-        }
+        assert counters.parquet == ParquetCounters(datasets=1, dismissed_discussions=1)
         # close the discussion
         close_discussion(dataset=dataset.repo_id, discussion_num=first_discussion.num)
         # call post_messages again
@@ -132,18 +124,14 @@ def test_post_messages_in_one_dataset(job_config: JobConfig) -> None:
             parquet_revision=job_config.discussions.parquet_revision,
         )
         # ensure no new discussion has been created
-        assert counters["parquet"] == {
-            "datasets": 1,
-            "new_discussions": 0,
-            "dismissed_discussions": 1,
-            "errors": 0,
-        }
+        assert counters.parquet == ParquetCounters(datasets=1, dismissed_discussions=1)
         third_discussion = fetch_bot_discussion(dataset=dataset.repo_id)
         assert third_discussion is not None
         assert first_discussion.num == third_discussion.num
         assert count_comments(third_discussion) == 1
 
 
+@pytest.mark.skip("the user name is invalid, see #8742 in moonlanding")
 def test_post_messages_with_two_datasets_in_one_namespace(job_config: JobConfig) -> None:
     with TemporaryDataset(prefix="dataset1") as dataset1, TemporaryDataset(prefix="dataset2") as dataset2:
         assert fetch_bot_discussion(dataset=dataset1.repo_id) is None
@@ -171,12 +159,7 @@ def test_post_messages_with_two_datasets_in_one_namespace(job_config: JobConfig)
             parquet_revision=job_config.discussions.parquet_revision,
         )
         # ensure one discussion has been created
-        assert counters["parquet"] == {
-            "datasets": 1,
-            "new_discussions": 1,
-            "dismissed_discussions": 0,
-            "errors": 0,
-        }
+        assert counters.parquet == ParquetCounters(datasets=1, new_discussions=1)
         discussion1 = fetch_bot_discussion(dataset=dataset1.repo_id)
         discussion2 = fetch_bot_discussion(dataset=dataset2.repo_id)
         discussion = discussion1 or discussion2
@@ -198,6 +181,7 @@ def test_post_messages_with_two_datasets_in_one_namespace(job_config: JobConfig)
         )
 
 
+@pytest.mark.skip("the user name is invalid, see #8742 in moonlanding")
 @pytest.mark.parametrize(
     "gated,private",
     [
@@ -229,12 +213,7 @@ def test_post_messages_in_private_or_gated_dataset(job_config: JobConfig, gated:
         # Normally: the cache should not contain private datasets, but a public
         # dataset can be switched to private, and for some reason, or during some
         # time, the cache can contain private datasets.
-        assert counters["parquet"] == {
-            "datasets": 1,
-            "new_discussions": 1,
-            "dismissed_discussions": 0,
-            "errors": 0,
-        }
+        assert counters.parquet == ParquetCounters(datasets=1, new_discussions=1)
         first_discussion = fetch_bot_discussion(dataset=dataset.repo_id)
         assert first_discussion is not None
         assert count_comments(first_discussion) == 1
@@ -248,6 +227,7 @@ def test_post_messages_in_private_or_gated_dataset(job_config: JobConfig, gated:
         )
 
 
+@pytest.mark.skip("the user name is invalid, see #8742 in moonlanding")
 def test_post_messages_for_outdated_response(job_config: JobConfig) -> None:
     with TemporaryDataset(prefix="dataset") as dataset:
         assert fetch_bot_discussion(dataset=dataset.repo_id) is None
@@ -268,12 +248,7 @@ def test_post_messages_for_outdated_response(job_config: JobConfig) -> None:
             parquet_revision=job_config.discussions.parquet_revision,
         )
         # ensure one discussion has been created, because the content was too old
-        assert counters["parquet"] == {
-            "datasets": 0,
-            "new_discussions": 0,
-            "dismissed_discussions": 0,
-            "errors": 0,
-        }
+        assert counters.parquet == ParquetCounters()
         assert fetch_bot_discussion(dataset=dataset.repo_id) is None
         # update the content
         upsert_response(
@@ -291,12 +266,7 @@ def test_post_messages_for_outdated_response(job_config: JobConfig) -> None:
             parquet_revision=job_config.discussions.parquet_revision,
         )
         # ensure one discussion has been created
-        assert counters["parquet"] == {
-            "datasets": 1,
-            "new_discussions": 1,
-            "dismissed_discussions": 0,
-            "errors": 0,
-        }
+        assert counters.parquet == ParquetCounters(datasets=1, new_discussions=1)
         first_discussion = fetch_bot_discussion(dataset=dataset.repo_id)
         assert first_discussion is not None
         assert count_comments(first_discussion) == 1