diff --git a/.github/workflows/outer_link_check.yml b/.github/workflows/outer_link_check.yml index 2ae2262183..7f541c197e 100644 --- a/.github/workflows/outer_link_check.yml +++ b/.github/workflows/outer_link_check.yml @@ -26,6 +26,17 @@ jobs: - name: check run: | { python3 .github/workflows/script/link_check.py --check-outer-link 3>&2 2>&1 1>&3 | tee check_result.txt; } 3>&2 2>&1 1>&3 + - name: recheck if the first check reported failures + # 1回目が失敗を報告した場合のみ、時間をあけてもう一度チェックし直す。 + # ランナー側のネットワーク不調など、1回の実行がまるごと一時障害に当たった + # ケースを誤検出しないためのワークフローレベルの再チェック。 + # 2回目の結果でcheck_result.txtを上書きし、両方で失敗したものだけを通知する。 + run: | + if [ -s check_result.txt ]; then + echo "First check reported failures. Waiting 5 minutes and rechecking to rule out transient/runner-side issues." + sleep 300 + { python3 .github/workflows/script/link_check.py --check-outer-link 3>&2 2>&1 1>&3 | tee check_result.txt; } 3>&2 2>&1 1>&3 + fi - name: create github issue when needed run: python3 .github/workflows/script/create_github_issue_when_not_empty.py check_result.txt ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.sha }} - name: detect link check check_result diff --git a/.github/workflows/script/link_check.py b/.github/workflows/script/link_check.py index f5320e9fca..8e5d71cdb0 100644 --- a/.github/workflows/script/link_check.py +++ b/.github/workflows/script/link_check.py @@ -8,32 +8,49 @@ import sys import time import random +from concurrent.futures import ThreadPoolExecutor urllib3.disable_warnings() -def retry_sleep(): - sec = random.uniform(20, 30) - time.sleep(sec) +# 外部リンクチェックの並列数。ネットワークI/OバウンドなのでスレッドでOK。 +# open-std.org (全URLの過半) が同時16接続を問題なく捌けることを実測して決定。 +OUTER_LINK_WORKERS = 16 -def check_url(url: str, retry: int = 5) -> tuple[bool, str]: +MAX_OUTER_LINK_RETRY = 3 + +def retry_sleep(retry: int) -> None: + # 指数バックオフ + ジッタ。残りretry回数から試行回数を求めて待機時間を増やし、 + # ジッタで再試行タイミングを分散させる (レート制限中のサーバへの集中を避ける)。 + attempt = MAX_OUTER_LINK_RETRY - retry + 1 # 1, 2, 3, ... + backoff = min(15 * (2 ** (attempt - 1)), 60) # 15, 30, 60 秒 (上限60秒) + time.sleep(backoff + random.uniform(0, 15)) # 0〜15秒のジッタ + +def check_url(url: str, retry: int = MAX_OUTER_LINK_RETRY) -> tuple[bool, str]: try: headers = {'User-agent': 'Mozilla/5.0'} - res = requests.head(url, headers=headers, verify=False, timeout=60.0) - if res.url: - if res.url == url: - return res.status_code != 404, "404" - return check_url(res.url) - else: - return res.status_code != 404, "404" + # パフォーマンスのため本文は取得せずHEADで確認する。 + # ただしallow_redirects=Trueでリダイレクトはrequestsに追わせ、最終的な + # ステータスで判定する (手動でres.urlを辿る再帰をやめ、リトライ回数が + # 途中でリセットされる問題を避ける)。 + res = requests.head(url, headers=headers, verify=False, timeout=60.0, + allow_redirects=True) + return res.status_code != 404, str(res.status_code) + except requests.exceptions.TooManyRedirects: + # リダイレクトループ (http↔httpsを行き来する等)。ブラウザではHSTS等で + # 到達できることが多く、リトライしても解消しないため、存在するものとして + # 扱う (ループ誤検出とムダな再試行を避ける)。 + # ※ TooManyRedirectsはRequestExceptionのサブクラスなので、下の汎用ハンドラ + # より前で捕捉する必要がある。 + return True, "redirect loop" except requests.exceptions.ConnectionError as e: if retry <= 0: return False, "requests.exceptions.ConnectionError : {} ".format(e) - retry_sleep() + retry_sleep(retry) return check_url(url, retry - 1) except requests.exceptions.RequestException as e: if retry <= 0: return False, "requests.exceptions.RequestException : {}".format(e) - retry_sleep() + retry_sleep(retry) return check_url(url, retry - 1) except Exception as e: return False, "unknown exception : {}".format(e) @@ -169,11 +186,28 @@ def check(check_inner_link: bool, check_outer_link: bool, url: str) -> bool: if len(url) > 0: outer_link_dict[url] = "" - for link, from_list in outer_link_dict.items(): + def check_one(item): + link, from_list = item exists, reason = check_url(link) - if not exists: - print("URL {} not found. {} from:{}".format(link, reason, from_list), file=sys.stderr) - found_error = True + return link, from_list, exists, reason + + # 1パス目: 失敗したURLだけ集める (ネットワークI/Oバウンドなのでスレッドで並列化) + failed = [] + with ThreadPoolExecutor(max_workers=OUTER_LINK_WORKERS) as executor: + for link, from_list, exists, reason in executor.map(check_one, outer_link_dict.items()): + if not exists: + failed.append((link, from_list)) + + # 2パス目: 一時的な障害 (ランナーのネットワーク輻輳やサイトの瞬断) を + # 誤検出しないよう、間をあけて失敗したURLのみ再チェックし、 + # 両方のパスで失敗したものだけを報告する + if failed: + time.sleep(120) + with ThreadPoolExecutor(max_workers=OUTER_LINK_WORKERS) as executor: + for link, from_list, exists, reason in executor.map(check_one, failed): + if not exists: + print("URL {} not found. {} from:{}".format(link, reason, from_list), file=sys.stderr) + found_error = True return not found_error diff --git a/lang/cpp17/extending_static_assert.md b/lang/cpp17/extending_static_assert.md index b0d6ac3386..f1269930eb 100644 --- a/lang/cpp17/extending_static_assert.md +++ b/lang/cpp17/extending_static_assert.md @@ -52,7 +52,7 @@ example_static_assert.cpp:5:3: error: static_assert failed ## この機能が必要になった背景・経緯 `assert` は条件式のみを引数に取るのに対し、`static_assert` には診断メッセージを提供しなければならなかった。 -[Boost.StaticAssert](http://www.boost.org/doc/libs/release/doc/html/boost_staticassert.html) は以下のような `BOOST_STATIC_ASSERT` マクロを提供しており、 +[Boost.StaticAssert](https://www.boost.org/doc/libs/latest/libs/config/doc/html/index.html) は以下のような `BOOST_STATIC_ASSERT` マクロを提供しており、 `static_assert` の診断メッセージを省略できた: ```cpp #define BOOST_STATIC_ASSERT(B) static_assert(B, #B) diff --git a/reference/random/bernoulli_distribution.md b/reference/random/bernoulli_distribution.md index 0571fa9f52..d84492a77c 100644 --- a/reference/random/bernoulli_distribution.md +++ b/reference/random/bernoulli_distribution.md @@ -118,4 +118,4 @@ int main() ## 参照 - [ベルヌーイ分布 - Wikipedia](https://ja.wikipedia.org/wiki/ベルヌーイ分布) -- [ベルヌーイ分布(Bernoulli distribution) - NtRand](http://www.ntrand.com/jp/bernoulli-distribution/) +- [ベルヌーイ分布(Bernoulli distribution) - NtRand](https://www.ntrand.com/ja/docs/gallery-of-distributions/bernoulli-distribution) diff --git a/reference/random/cauchy_distribution.md b/reference/random/cauchy_distribution.md index 094c211178..afc7a2d058 100644 --- a/reference/random/cauchy_distribution.md +++ b/reference/random/cauchy_distribution.md @@ -125,4 +125,4 @@ int main() ### 参考 - [コーシー分布 - Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%BC%E3%82%B7%E3%83%BC%E5%88%86%E5%B8%83) -- [コーシー分布 - NtRand](http://www.ntrand.com/jp/cauchy-distribution/) +- [コーシー分布 - NtRand](https://www.ntrand.com/ja/docs/gallery-of-distributions/cauchy-distribution) diff --git a/reference/random/exponential_distribution.md b/reference/random/exponential_distribution.md index b8f051c5c3..74238337ea 100644 --- a/reference/random/exponential_distribution.md +++ b/reference/random/exponential_distribution.md @@ -188,6 +188,6 @@ Phone call after 2.60918 minute wait ### 参考 - [指数分布 - Wikipedia](https://ja.wikipedia.org/wiki/指数分布) -- [指数分布 - NtRand](http://www.ntrand.com/jp/exponential-distribution/) +- [指数分布 - NtRand](https://www.ntrand.com/ja/docs/gallery-of-distributions/exponential-distribution) - [指数分布 - 統計学自習ノート](https://web.archive.org/web/20241204102039/http://aoki2.si.gunma-u.ac.jp/lecture/Bunpu/exponential.html) - [指数分布とポアソン分布のいけない関係](http://www.slideshare.net/teramonagi/ss-11296227) diff --git a/reference/random/extreme_value_distribution.md b/reference/random/extreme_value_distribution.md index d2a843602b..023d00d241 100644 --- a/reference/random/extreme_value_distribution.md +++ b/reference/random/extreme_value_distribution.md @@ -128,4 +128,3 @@ int main() ### 参考 - [極値分布](https://ja.wikipedia.org/wiki/極値分布) - [一般化極値分布 - MATLAB & Simulink - MathWorks 日本](https://jp.mathworks.com/help/stats/generalized-extreme-value-distribution.html) -- [極値分布とその応用に関する研究](http://www.seto.nanzan-u.ac.jp/msie/gr-thesis/ms/2005/osaki/02mm042.pdf) diff --git a/reference/random/lognormal_distribution.md b/reference/random/lognormal_distribution.md index c114f3cf57..7559dcd886 100644 --- a/reference/random/lognormal_distribution.md +++ b/reference/random/lognormal_distribution.md @@ -125,5 +125,5 @@ int main() ### 参考 - [対数正規分布 - Wikipedia](https://ja.wikipedia.org/wiki/%E5%AF%BE%E6%95%B0%E6%AD%A3%E8%A6%8F%E5%88%86%E5%B8%83) -- [対数正規分布 - NtRand](http://www.ntrand.com/jp/log-normal-distribution/) +- [対数正規分布 - NtRand](https://www.ntrand.com/ja/docs/gallery-of-distributions/log-normal-distribution) - [対数正規分布の仕組み - 小人さんの妄想](http://d.hatena.ne.jp/rikunora/20100418/p1) diff --git a/reference/random/poisson_distribution.md b/reference/random/poisson_distribution.md index 3456778584..f462e43318 100644 --- a/reference/random/poisson_distribution.md +++ b/reference/random/poisson_distribution.md @@ -167,7 +167,7 @@ Month 12: 1 earthquake(s) ### 参考 - [ポワソン分布 - Wikipedia](https://ja.wikipedia.org/wiki/ポアソン分布) - [ポアソン分布 - 統計・データ解析](https://okumuralab.org/~okumura/stat/poisson.html) -- [ポアソン分布 - NtRand](http://www.ntrand.com/jp/poisson-distribution/) +- [ポアソン分布 - NtRand](https://www.ntrand.com/ja/docs/gallery-of-distributions/poisson-distribution) - [[データ分析]ポアソン分布 ~ 100年に1人の天才は何人現れる?:やさしい確率分布 - @IT](https://atmarkit.itmedia.co.jp/ait/articles/2407/11/news002.html) diff --git a/reference/random/weibull_distribution.md b/reference/random/weibull_distribution.md index b47b886653..9c83b3bfa4 100644 --- a/reference/random/weibull_distribution.md +++ b/reference/random/weibull_distribution.md @@ -129,5 +129,5 @@ int main() ### 参考 - [ワイブル分布 - Wikipedia](https://ja.wikipedia.org/wiki/ワイブル分布) -- [ワイブル分布 = NtRand](http://www.ntrand.com/jp/weibull-distribution/) +- [ワイブル分布 = NtRand](https://www.ntrand.com/ja/docs/gallery-of-distributions/weibull-distribution) - [疲労や破壊現象とワイブル分布](http://web.archive.org/web/20220706102605/http://www.mogami.com/notes/weibull.html)