Compare commits

...

224 Commits

Author SHA1 Message Date
Kevin Wan
8478474f7f update readme (#673) 2021-05-08 21:55:14 +08:00
anqiansong
df5ae9507f replace antlr module (#672)
* replace antlr module

* refactor version of antlr
2021-05-08 21:35:27 +08:00
noel
faf4d7e3bb modify the order of PrometheusHandler (#670)
* modify the order of PrometheusHandler

* modify the order of PrometheusHandler
2021-05-08 17:11:16 +08:00
anqiansong
f64fe5eb5e fix antlr mod (#669) 2021-05-08 00:03:01 +08:00
heyanfu
97d889103a fix some typo (#667) 2021-05-04 21:33:08 +08:00
Kevin Wan
9a44310d00 update wechat qrcode (#665) 2021-05-02 15:06:16 +08:00
Kevin Wan
06eeef2cf3 disable prometheus if not configured (#663) 2021-04-30 15:09:49 +08:00
Kevin Wan
9adc7d4cb9 fix comment function names (#649) 2021-04-23 11:56:41 +08:00
Kevin Wan
006f78c3d5 add go-zero users (#643) 2021-04-21 10:24:15 +08:00
Kevin Wan
64a8e65f4a update readme (#640) 2021-04-20 23:57:57 +08:00
anqiansong
8fd1e76d29 update readme (#638) 2021-04-19 14:37:47 +08:00
heyanfu
0466af5e49 optimize code (#637) 2021-04-18 22:49:03 +08:00
heyanfu
7405d7f506 spelling mistakes (#634) 2021-04-17 20:15:19 +08:00
Bo-Yi Wu
afd9ff889e chore: update code format. (#628) 2021-04-15 19:49:17 +08:00
另维64
7e087de6e6 doc: fix spell mistake (#627) 2021-04-14 17:58:27 +08:00
Kevin Wan
5aded99df5 update go-zero users (#623) 2021-04-13 14:38:40 +08:00
Kevin Wan
08fb980ad2 add syncx.Guard func (#620) 2021-04-13 00:04:19 +08:00
Kevin Wan
b94d7aa532 update readme (#617) 2021-04-10 19:19:05 +08:00
Kevin Wan
ee630b8b57 add code coverage (#615)
* add code coverage

* simplify redis code
2021-04-09 22:40:43 +08:00
Kevin Wan
bd82b7d8de add FAQs in readme (#612) 2021-04-09 18:59:17 +08:00
Kevin Wan
3d729c77a6 update go-zero users (#611) 2021-04-09 14:16:31 +08:00
Kevin Wan
e944b59bb3 update go-zero users (#609)
* add go-zero users registry notes

* update go-zero users

* fix typo
2021-04-09 10:43:47 +08:00
Kevin Wan
54b5e3f4b2 add go-zero users registry notes (#608) 2021-04-08 22:44:41 +08:00
Kevin Wan
b913229028 add go-zero users (#607) 2021-04-08 22:30:45 +08:00
Kevin Wan
9963ffb1c1 simplify redis tls implementation (#606) 2021-04-08 18:19:36 +08:00
r00mz
8cb6490724 redis增加tls支持 (#595)
* redis连接增加支持tls选项

* 优化redis tls config 写法

* redis增加tls支持

* 增加redis tls测试用例,但redis tls local server不支持,测试用例全部NotNil

Co-authored-by: liuyi <liuyi@fangyb.com>
Co-authored-by: yi.liu <yi.liu@xshoppy.com>
2021-04-07 20:44:16 +08:00
Kevin Wan
05e37ee20f refactor - remove ShrinkDeadline, it's the same as context.WithTimeout (#599) 2021-04-05 22:59:24 +08:00
zjbztianya
d88da4cc88 Replace contextx.ShrinkDeadline with context.WithTimeout (#598) 2021-04-05 21:20:35 +08:00
Oraoto
425430f67c Simplify contextx.ShrinkDeadline (#596) 2021-04-03 21:25:32 +08:00
Zcc、
4e0d91f6c0 fix (#592)
Co-authored-by: zhoudeyu <zhoudeyu@xiaoheiban.cn>
2021-04-01 18:42:50 +08:00
Kevin Wan
8584351b6d update regression test comment (#590) 2021-03-30 21:23:07 +08:00
Kevin Wan
b19c5223a9 update regression test comment (#589) 2021-03-30 20:53:35 +08:00
bittoy
99a2d95433 remove rt mode log (#587) 2021-03-30 20:45:55 +08:00
Ted Chen
9db222bf5b fix a simple typo (#588) 2021-03-29 23:35:49 +08:00
Kevin Wan
ac648d08cb fix typo (#586) 2021-03-28 22:10:07 +08:00
Kevin Wan
6df7fa619c fix typo (#585) 2021-03-28 21:20:04 +08:00
Kevin Wan
bbb4ce586f fix golint issues (#584) 2021-03-28 20:42:11 +08:00
anqiansong
888551627c optimize code (#579)
* optimize code

* optimize returns & unit test
2021-03-27 17:33:17 +08:00
Kevin Wan
bd623aaac3 support postgresql (#583)
support postgresql
2021-03-27 17:14:32 +08:00
Kevin Wan
9e6c2ba2c0 avoid goroutine leak after timeout (#575) 2021-03-21 16:54:34 +08:00
Kevin Wan
c0db8d017d gofmt logs (#574) 2021-03-20 16:40:09 +08:00
TonyWang
52b4f8ca91 add timezone and timeformat (#572)
* add timezone and timeformat

* rm time zone and keep time format

Co-authored-by: Tony Wang <tonywang.data@gmail.com>
2021-03-20 16:36:19 +08:00
Kevin Wan
4884a7b3c6 zrpc timeout & unit tests (#573)
* zrpc timeout & unit tests
2021-03-19 18:41:26 +08:00
Kevin Wan
3c6951577d make hijack more stable (#565) 2021-03-15 20:11:09 +08:00
Kevin Wan
fcd15c9b17 refactor, and add comments to describe graceful shutdown (#564) 2021-03-14 08:51:10 +08:00
Kevin Wan
155e6061cb fix golint issues (#561) 2021-03-12 23:08:04 +08:00
anqiansong
dda7666097 Feature mongo gen (#546)
* add feature: mongo code generation

* upgrade version

* update doc

* format code

* update update.tpl of mysql
2021-03-12 17:49:28 +08:00
hanhotfox
c954568b61 Hdel support for multiple key deletion (#542)
* Hdel support for multiple key deletion

* Hdel field -> fields

Co-authored-by: duanyan <duanyan@xiaoheiban.cn>
2021-03-12 17:47:21 +08:00
Kevin Wan
c2acc43a52 add important notes in readme (#560) 2021-03-12 16:48:25 +08:00
Kevin Wan
1a1a6f5239 add http hijack methods (#555) 2021-03-09 21:30:45 +08:00
anqiansong
60c7edf8f8 fix spelling (#551) 2021-03-08 18:23:12 +08:00
Kevin Wan
7ad86a52f3 update doc link (#552) 2021-03-08 17:56:03 +08:00
kingxt
1e4e5a02b2 rename (#543) 2021-03-04 17:13:07 +08:00
Kevin Wan
39540e21d2 fix golint issues (#540) 2021-03-03 17:16:09 +08:00
hexiaoen
b321622c95 暴露redis EvalSha 以及ScriptLoad接口 (#538)
Co-authored-by: shanehe <shanehe@zego.im>
2021-03-03 17:09:27 +08:00
kingxt
a25cba5380 fix collection breaker (#537)
* fix collection breaker

* optimized

* optimized

* optimized
2021-03-03 10:44:29 +08:00
Kevin Wan
f01472c9ea fix golint issues (#535) 2021-03-02 11:02:57 +08:00
Kevin Wan
af531cf264 fix golint issues (#533) 2021-03-02 00:11:18 +08:00
Kevin Wan
c4b2cddef7 fix golint issues (#532) 2021-03-02 00:04:12 +08:00
Kevin Wan
51de0d0620 fix golint issues in zrpc (#531) 2021-03-01 23:52:44 +08:00
anqiansong
dd393351cc patch 1.1.5 (#530) 2021-03-01 21:14:07 +08:00
Kevin Wan
655ae8034c fix golint issues in rest (#529) 2021-03-01 19:15:35 +08:00
anqiansong
d894b88c3e feature 1.1.5 (#411) 2021-03-01 17:29:07 +08:00
Kevin Wan
791e76bcf0 fix broken build (#528) 2021-02-28 23:53:58 +08:00
Kevin Wan
c566b5ff82 fix golint issues in core/stores (#527) 2021-02-28 23:02:49 +08:00
Kevin Wan
490241d639 fix golint issues in core/syncx (#526) 2021-02-28 16:16:22 +08:00
Kevin Wan
f02711a9cb golint core/discov (#525) 2021-02-27 23:56:18 +08:00
Kevin Wan
ad32f9de23 fix golint issues in core/threading (#524) 2021-02-26 16:27:04 +08:00
Kevin Wan
f309e9f80c fix golint issues in core/utils (#520)
* fix golint issues in core/utils

* fix golint issues in core/trace

* fix golint issues in core/trace
2021-02-26 16:20:47 +08:00
hao
2087ac1e89 修正http转发头字段值错误 (#521) 2021-02-26 16:17:30 +08:00
kingxt
e6ef1fca12 Code optimized (#523)
* optimized markdown generator

* optimized markdown generator

* optimized markdown generator

* add more comment

* add comment

* add comment

* add comments for rpc tool

* add comments for model tool

* add comments for model tool

* add comments for model tool

* add comments for config tool

* add comments for config tool

* add comments

* add comments

* add comments

* add comments

* add comment

* remove rpc main head info

* add comment

* optimized

Co-authored-by: anqiansong <anqiansong@xiaoheiban.cn>
2021-02-26 16:11:47 +08:00
Kevin Wan
ef146cf5ba fix golint issues in core/timex (#517) 2021-02-24 16:27:11 +08:00
Kevin Wan
04b0f26182 fix golint issues in core/stringx (#516) 2021-02-24 16:09:07 +08:00
Kevin Wan
acdaee0fb6 fix golint issues in core/stat (#515)
* change to use ServiceGroup to make it more clear

* fix golint issues in core/stat
2021-02-24 15:13:56 +08:00
Kevin Wan
56ad4776d4 fix misspelling (#513) 2021-02-23 13:53:19 +08:00
Kevin Wan
904d168f18 fix golint issues in core/service (#512) 2021-02-22 22:43:24 +08:00
Kevin Wan
4bd4981bfb fix golint issues in core/search (#509) 2021-02-22 18:58:03 +08:00
Kevin Wan
90562df826 fix golint issues in core/rescue (#508) 2021-02-22 16:47:02 +08:00
Kevin Wan
497762ab47 fix golint issues in core/queue (#507) 2021-02-22 16:38:42 +08:00
Kevin Wan
6e4c98e52d fix golint issues in core/prometheus (#506) 2021-02-22 14:55:04 +08:00
Kevin Wan
b4bb5c0323 fix broken links in readme (#505) 2021-02-22 14:13:33 +08:00
Kevin Wan
a58fac9000 fix golint issues in core/prof (#503) 2021-02-22 10:20:54 +08:00
Kevin Wan
d84e3d4b53 fix golint issues in core/proc (#502) 2021-02-22 10:07:39 +08:00
Kevin Wan
221f923fae fix golint issues in core/netx (#501) 2021-02-22 09:56:56 +08:00
Kevin Wan
bbb9126302 fix golint issues in core/mr (#500) 2021-02-22 09:47:06 +08:00
Kevin Wan
e7c9ef16fe fix golint issues in core/metric (#499) 2021-02-21 21:18:07 +08:00
Kevin Wan
8872d7cbd3 fix golint issues in core/mathx (#498) 2021-02-21 20:47:01 +08:00
Kevin Wan
334ee4213f fix golint issues in core/mapping (#497) 2021-02-20 23:18:22 +08:00
Kevin Wan
226513ed60 fix golint issues in core/logx (#496) 2021-02-20 22:45:58 +08:00
Kevin Wan
dac00d10c1 fix golint issues in core/load (#495) 2021-02-20 22:02:09 +08:00
Kevin Wan
84d2b6f8f5 fix golint issues in core/limit (#494) 2021-02-20 21:55:54 +08:00
kingxt
f98c9246b2 Code optimized (#493) 2021-02-20 19:50:03 +08:00
Kevin Wan
059027bc9d fix golint issues in core/lang (#492) 2021-02-20 18:21:23 +08:00
Kevin Wan
af68caeaf6 fix golint issues in core/jsonx (#491) 2021-02-20 16:59:31 +08:00
Zcc、
fdeacfc89f add redis bitmap command (#490)
Co-authored-by: zhoudeyu <zhoudeyu@xiaoheiban.cn>
2021-02-20 16:26:49 +08:00
Kevin Wan
5b33dd59d9 fix golint issues in core/jsontype (#489) 2021-02-20 15:07:49 +08:00
Kevin Wan
1f92bfde6a fix golint issues in core/iox (#488) 2021-02-19 18:40:26 +08:00
Kevin Wan
0c094cb2d7 fix golint issues in core/hash (#487) 2021-02-19 18:14:34 +08:00
Kevin Wan
f238290dd3 fix golint issues in core/fx (#486) 2021-02-19 17:49:39 +08:00
Kevin Wan
c376ffc351 fix golint issues in core/filex (#485) 2021-02-19 14:30:38 +08:00
Kevin Wan
802549ac7c fix golint issues in core/executors (#484) 2021-02-19 12:03:05 +08:00
Zcc、
72580dee38 redis add bitcount (#483)
Co-authored-by: zhoudeyu <zhoudeyu@xiaoheiban.cn>
2021-02-19 11:41:01 +08:00
Kevin Wan
086113c843 prevent negative timeout settings (#482)
* prevent negative timeout settings

* fix misleading comment
2021-02-19 10:44:39 +08:00
HarryWang29
d239952d2d zrpc client support block (#412) 2021-02-19 10:24:03 +08:00
Kevin Wan
7472d1e70b fix golint issues in core/errorx (#480) 2021-02-19 10:08:38 +08:00
Kevin Wan
2446d8a668 fix golint issues in core/discov (#479) 2021-02-18 22:56:35 +08:00
Kevin Wan
f6894448bd fix golint issues in core/contextx (#477) 2021-02-18 18:00:20 +08:00
Kevin Wan
425be6b4a1 fix golint issues in core/conf (#476) 2021-02-18 15:56:19 +08:00
Kevin Wan
457048bfac fix golint issues in core/collection, refine cache interface (#475) 2021-02-18 15:49:56 +08:00
kingxt
f14ab70035 Code optimized (#474)
* optimized markdown generator

* optimized markdown generator

* optimized markdown generator

* optimized markdown generator
2021-02-18 15:08:20 +08:00
Kevin Wan
8f1c88e07d fix golint issues in core/codec (#473) 2021-02-18 14:11:09 +08:00
Kevin Wan
9602494454 fix issue #469 (#471) 2021-02-17 21:42:22 +08:00
Kevin Wan
38abfb80ed fix gocyclo warnings (#468) 2021-02-17 14:01:05 +08:00
Kevin Wan
87938bcc09 fix golint issues in core/cmdline (#467) 2021-02-17 11:08:30 +08:00
Kevin Wan
8ebf6750b9 fix golint issues in core/breaker (#466) 2021-02-17 10:45:55 +08:00
Kevin Wan
6f92daae12 fix golint issues in core/bloom (#465) 2021-02-17 09:58:35 +08:00
Kevin Wan
80e1c85b50 add more tests for service (#463) 2021-02-11 23:48:19 +08:00
Kevin Wan
395a1db22f add more tests for rest (#462) 2021-02-10 23:08:48 +08:00
bittoy
28009c4224 Update serviceconf.go (#460)
add regression environment config
2021-02-09 15:35:50 +08:00
Kevin Wan
211f3050e9 fix golint issues (#459) 2021-02-09 14:10:38 +08:00
Kevin Wan
03b5fd4a10 fix golint issues (#458) 2021-02-09 14:03:19 +08:00
Kevin Wan
5e969cbef0 fix golint issues, else blocks (#457) 2021-02-09 13:50:21 +08:00
Kevin Wan
42883d0899 fix golint issues, redis methods (#455) 2021-02-09 10:58:11 +08:00
Kevin Wan
06f6dc9937 fix golint issues, package comments (#454) 2021-02-08 22:31:52 +08:00
Kevin Wan
1789b12db2 move examples into zero-examples (#453)
* move examples to zero-examples

* tidy go.mod

* add examples refer in readme
2021-02-08 22:23:36 +08:00
Kevin Wan
c7f3e6119d remove images, use zero-doc instead (#452) 2021-02-08 21:57:40 +08:00
Kevin Wan
54414db91d fix golint issues, exported doc (#451) 2021-02-08 21:31:56 +08:00
Kevin Wan
9b0625bb83 fix golint issues (#450) 2021-02-08 17:08:40 +08:00
Kevin Wan
0dda05fd57 add api doc (#449) 2021-02-08 11:10:55 +08:00
Kevin Wan
5b79ba2618 add discov tests (#448) 2021-02-07 20:24:47 +08:00
Kevin Wan
22a1fa649e remove etcd facade, added for testing purpose (#447) 2021-02-07 19:07:15 +08:00
Kevin Wan
745e76c335 add more tests for stores (#446) 2021-02-07 17:22:47 +08:00
Kevin Wan
852891dbd8 add more tests for stores (#445) 2021-02-07 15:27:01 +08:00
Kevin Wan
316195e912 add more tests for mongoc (#443) 2021-02-07 14:41:00 +08:00
Kevin Wan
8e889d694d add more tests for sqlx (#442)
* add more tests for sqlx

* add more tests for sqlx
2021-02-07 11:54:41 +08:00
Kevin Wan
ec6132b754 add more tests for zrpc (#441) 2021-02-06 12:25:45 +08:00
Kevin Wan
c282bb1d86 add more tests for sqlx (#440) 2021-02-05 22:53:21 +08:00
Kevin Wan
d04b54243d add more tests for proc (#439) 2021-02-05 15:11:27 +08:00
Kevin Wan
b88ba14597 fixes issue #425 (#438) 2021-02-05 13:32:56 +08:00
理工男
7b3c3de35e ring struct add lock (#434)
Co-authored-by: liuhuan210 <liuhuan210@jd.com>
2021-02-03 21:41:10 +08:00
Kevin Wan
abab7c2852 Update readme.md 2021-02-03 15:43:35 +08:00
Kevin Wan
30f5ab0b99 update readme for broken links (#432) 2021-02-03 12:02:22 +08:00
foyon
8b273a075c Support redis command Rpop (#431)
* ss

* ss

* add go-zero:stores:redis-command:Rpop and redis_test

* Delete 1.go

* support redis command Rpop

Co-authored-by: fanhongyi <fanhongyi@tal.com>
2021-02-03 10:19:42 +08:00
Liang Zheng
76026fc211 fix readme.md error (#429)
Signed-off-by: Liang Zheng <microyahoo@163.com>
2021-02-03 10:18:28 +08:00
Hkesd
04284e31cd support hscan in redis (#428) 2021-02-02 17:02:18 +08:00
Kevin Wan
c3b9c3c5ab use english readme as default, because of github ranking (#427) 2021-02-02 16:58:45 +08:00
FengZhang
a8b550e7ef Modify the http content-length max range : 30MB --> 32MB (#424)
Because we are programmer :)
2021-01-30 18:49:33 +08:00
FengZhang
cbfbebed00 modify the maximum content-length to 30MB (#413) 2021-01-29 22:14:48 +08:00
kingxt
2b07f22672 optimize code (#417)
* optimize code

* optimize code

* optimize code

* optimize code
2021-01-26 17:37:22 +08:00
Kevin Wan
a784982030 support zunionstore in redis (#410) 2021-01-21 21:03:24 +08:00
Kevin Wan
ebec5aafab use env if necessary in loading config (#409) 2021-01-21 19:33:34 +08:00
Kevin Wan
572b32729f update goctl version to 1.1.3 (#402) 2021-01-18 16:34:00 +08:00
kingxt
43e712d86a fix type convert error (#395) 2021-01-16 18:24:11 +08:00
kingxt
4db20677f7 optimized (#392) 2021-01-15 11:36:37 +08:00
Kevin Wan
6887fb22de add more tests for codec (#391) 2021-01-14 23:39:44 +08:00
Kevin Wan
50fbdbcfd7 update readme (#390) 2021-01-14 22:26:31 +08:00
ALMAS
c77b8489d7 Update periodicalexecutor.go (#389) 2021-01-14 22:20:09 +08:00
Kevin Wan
eca4ed2cc0 format code (#386) 2021-01-14 13:24:24 +08:00
Kevin Wan
744c18b7cb simplify cgroup controller separation (#384) 2021-01-13 20:58:33 +08:00
miaogaolin
8d6f6f933e fix cgroup bug (#380) 2021-01-13 20:39:57 +08:00
Kevin Wan
37c3b9f5c1 make sure unlock safe even if listeners panic (#383)
* make sure unlock safe even if listeners panic

* fix #378

* fix #378
2021-01-13 18:43:42 +08:00
卢永杰
1f1dcd16e6 fix server.start return nil points (#379)
Co-authored-by: luyongjie <luyongjie@37.com>
2021-01-13 18:40:39 +08:00
文杰
3285436f75 f-fix spell (#381)
Co-authored-by: chenwenjie <chenwenjie@zzstc.cn>
2021-01-13 18:07:31 +08:00
kingxt
7f49bd8a31 code optimized (#382) 2021-01-13 16:37:33 +08:00
kingxt
9cd2015661 fix inner type generate error (#377)
* fix point type bug

* optimized

* fix inner type error
2021-01-13 11:54:53 +08:00
kingxt
cf3a1020b0 Java optimized (#376)
* optiimzed java gen

* optiimzed java gen

* fix
2021-01-12 14:14:49 +08:00
kingxt
ee19fb736b feature: refactor api parse to g4 (#365)
* feature: refactor api parse to g4

* new g4 parser

* add CHANGE_LOG.MD

* refactor

* fix byte bug

* refactor

* optimized

* optimized

* revert

* update readme.md

* update readme.md

* update readme.md

* update readme.md

* remove no need

* fix java gen

* add upgrade

* resolve confilits

Co-authored-by: anqiansong <anqiansong@xiaoheiban.cn>
2021-01-11 15:10:51 +08:00
Kevin Wan
b0ccfb8eb4 add more tests for conf (#371) 2021-01-10 21:53:16 +08:00
Kevin Wan
444e5a711f update doc to use table to render plugins (#370) 2021-01-09 19:54:34 +08:00
Kevin Wan
8774d72ddb remove duplicated code in goctl (#369) 2021-01-09 00:17:23 +08:00
HarryWang29
e3fcdbf040 fix return in for (#367)
Co-authored-by: HarryWang29 <wrz890829@gmail.com>
2021-01-08 22:47:27 +08:00
Kevin Wan
2854ca03b4 update goctl version to 1.1.3 (#364) 2021-01-08 14:02:59 +08:00
anqiansong
6c624a6ed0 Feature model fix (#362)
* fix sql builderx adding raw string quotation marks incompatibility bug

* add unit test

* remove comments

* fix sql builderx adding raw string quotation marks incompatibility bug
2021-01-08 12:01:21 +08:00
Kevin Wan
57b73d8b49 make sure offset less than size even it's checked inside (#354) 2021-01-05 16:06:36 +08:00
Kevin Wan
a79cee12ee add godoc for RollingWindow (#351) 2021-01-04 22:43:55 +08:00
zjbztianya
7a921f66e6 simple rolling windows code (#346) 2021-01-04 22:11:18 +08:00
kingxt
12e235efb0 optimized goctl format (#336)
* fix format

* refactor

* refactor

* optimized

* refactor

* refactor

* refactor

* add js path prefix
2021-01-04 18:59:48 +08:00
Kevin Wan
01060cf16d close issue of #337 (#347) 2021-01-04 16:36:27 +08:00
Kevin Wan
0786862a35 align bucket boundary to interval in rolling window (#345) 2021-01-04 11:17:59 +08:00
Kevin Wan
efa43483b2 fix potential data race in PeriodicalExecutor (#344)
* fix potential data race in PeriodicalExecutor

* add comment
2021-01-03 20:56:17 +08:00
Kevin Wan
771371e051 simplify rolling window code, and make tests run faster (#343) 2021-01-03 20:47:29 +08:00
zjbztianya
2ee95f8981 fix rolling window bug (#340) 2021-01-03 20:27:47 +08:00
Kevin Wan
5bc01e4bfd set guarded to false only on quitting background flush (#342)
* set guarded to false only on quitting background flush

* set guarded to false only on quitting background flush, cont.
2021-01-03 19:54:11 +08:00
Kevin Wan
510e966982 simplify periodical executor background routine (#339) 2021-01-03 14:02:51 +08:00
Kevin Wan
10e3b8ac80 optimize code that fixes issue #317 (#338) 2021-01-02 19:01:37 +08:00
Kevin Wan
04059bbf5a add discord chat group in readme 2021-01-02 18:35:33 +08:00
weibobo
d643007c79 fix bug #317 (#335)
* fix bug #317.
* add counter for current task. If it's bigger then zero, do not quit background thread

* Revert "fix issue #317 (#331)"

This reverts commit fc43876cc5.
2021-01-02 18:04:04 +08:00
Kevin Wan
fc43876cc5 fix issue #317 (#331) 2021-01-01 13:24:28 +08:00
FengZhang
a926cb514f modify the goctl gensvc template (#323) 2020-12-30 10:05:26 +08:00
kingxt
25cab2f273 Java (#327)
* add g4 file

* new define api by g4

* reactor parser to g4gen

* add syntax parser & test

* add syntax parser & test

* add syntax parser & test

* update g4 file

* add import parse & test

* ractor AT lexer

* panic with error

* revert AT

* update g4 file

* update g4 file

* update g4 file

* optimize parser

* update g4 file

* parse info

* optimized java generator

* revert

* optimize java generator

* update java generator

* update java generator

* update java generator

* update java generator

Co-authored-by: anqiansong <anqiansong@xiaoheiban.cn>
2020-12-29 17:50:41 +08:00
Kevin Wan
8d2e2753a2 simplify http.Flusher implementation (#326)
* simplify code with http.Flusher type conversion

* simplify code with http.Flusher type conversion, better version
2020-12-29 15:02:36 +08:00
Kevin Wan
cc4c50e3eb fix broken link. 2020-12-29 11:54:32 +08:00
Kevin Wan
751072bdb0 fix broken doc link 2020-12-29 11:52:55 +08:00
Kevin Wan
e97e1f10db simplify code with http.Flusher type conversion (#325)
* simplify code with http.Flusher type conversion

* simplify code with http.Flusher type conversion, better version
2020-12-29 10:25:55 +08:00
jichangyun
0bd2a0656c The ResponseWriters defined in rest.handler add Flush interface. (#318) 2020-12-28 21:30:24 +08:00
Kevin Wan
71a2b20301 add more tests for prof (#322) 2020-12-27 14:45:14 +08:00
Kevin Wan
8df7de94e3 add more tests for zrpc (#321) 2020-12-27 14:08:24 +08:00
Kevin Wan
bf21203297 add more tests (#320) 2020-12-27 12:26:31 +08:00
Kevin Wan
ae98375194 add more tests (#319) 2020-12-26 20:30:02 +08:00
Kevin Wan
82d1ccf376 fixes #286 (#315) 2020-12-25 19:47:27 +08:00
Kevin Wan
bb6d49c17e add go report card back (#313)
* add go report card back

* avoid test failure, run tests sequentially
2020-12-25 12:09:59 +08:00
Kevin Wan
ed735ec47c Update codeql-analysis.yml
disable python code analysis, python code is in examples.
2020-12-25 12:09:43 +08:00
Kevin Wan
ba4bac3a03 format code (#312) 2020-12-25 11:53:37 +08:00
FengZhang
08433d7e04 add config load support env var (#309) 2020-12-25 11:42:19 +08:00
anqiansong
a3b525b50d feature model fix (#296)
* add raw stirng quote for sql field

* remove unused code
2020-12-21 09:43:32 +08:00
Kevin Wan
097f6886f2 Update readme.md 2020-12-15 23:47:41 +08:00
Kevin Wan
07a1549634 add wechat micro practice qrcode image (#289) 2020-12-14 17:49:58 +08:00
Kevin Wan
befca26c58 Update readme.md
add goproxy.cn download badge
2020-12-13 00:02:32 +08:00
Kevin Wan
3556a2eef4 Update readme-en.md
goreportcard is not working, submitted an issue to them.
2020-12-12 23:40:26 +08:00
Kevin Wan
807765f77e Update readme.md
goreportcard is not working, submitted a issue to them.
2020-12-12 23:39:28 +08:00
Kevin Wan
e44584e549 Create codeql-analysis.yml 2020-12-12 23:01:15 +08:00
Kevin Wan
acd48f0abb optimize dockerfile generation (#284) 2020-12-12 16:53:06 +08:00
kingxt
f919bc6713 refactor (#283) 2020-12-12 11:18:22 +08:00
Kevin Wan
a0030b8f45 format dockerfile on non-chinese mode (#282) 2020-12-12 10:13:33 +08:00
Kevin Wan
a5f0cce1b1 Update readme-en.md 2020-12-12 09:06:09 +08:00
Kevin Wan
4d13dda605 add EXPOSE in dockerfile generation (#281) 2020-12-12 08:18:01 +08:00
songmeizi
b56cc8e459 optimize test case of TestRpcGenerate (#279)
Co-authored-by: anqiansong <anqiansong@xiaoheiban.cn>
2020-12-11 21:57:04 +08:00
Kevin Wan
c435811479 fix gocyclo warnings (#278) 2020-12-11 20:57:48 +08:00
Kevin Wan
c686c93fb5 fix dockerfile generation bug (#277) 2020-12-11 20:31:31 +08:00
Kevin Wan
da8f76e6bd add category docker & kube (#276) 2020-12-11 18:53:40 +08:00
Kevin Wan
99596a4149 fix issue #266 (#275)
* optimize dockerfile

* fix issue #266
2020-12-11 16:12:33 +08:00
wayne
ec2a9f2c57 fix tracelogger_test TestTraceLog (#271) 2020-12-10 17:04:57 +08:00
Kevin Wan
fd73ced6dc optimize dockerfile (#272) 2020-12-10 16:21:06 +08:00
Kevin Wan
5071736ab4 fmt code (#270) 2020-12-10 15:16:13 +08:00
727 changed files with 23934 additions and 14427 deletions

67
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '18 19 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

2
.gitignore vendored
View File

@@ -4,6 +4,7 @@
# Unignore all with extensions # Unignore all with extensions
!*.* !*.*
!**/Dockerfile !**/Dockerfile
!**/Makefile
# Unignore all dirs # Unignore all dirs
!*/ !*/
@@ -12,7 +13,6 @@
.idea .idea
**/.DS_Store **/.DS_Store
**/logs **/logs
!Makefile
# gitlab ci # gitlab ci
.cache .cache

View File

@@ -27,43 +27,43 @@ return true
` `
) )
// ErrTooLargeOffset indicates the offset is too large in bitset.
var ErrTooLargeOffset = errors.New("too large offset") var ErrTooLargeOffset = errors.New("too large offset")
type ( type (
BitSetProvider interface { // A Filter is a bloom filter.
Filter struct {
bits uint
bitSet bitSetProvider
}
bitSetProvider interface {
check([]uint) (bool, error) check([]uint) (bool, error)
set([]uint) error set([]uint) error
} }
BloomFilter struct {
bits uint
bitSet BitSetProvider
}
) )
// New create a BloomFilter, store is the backed redis, key is the key for the bloom filter, // New create a Filter, store is the backed redis, key is the key for the bloom filter,
// bits is how many bits will be used, maps is how many hashes for each addition. // bits is how many bits will be used, maps is how many hashes for each addition.
// best practices: // best practices:
// elements - means how many actual elements // elements - means how many actual elements
// when maps = 14, formula: 0.7*(bits/maps), bits = 20*elements, the error rate is 0.000067 < 1e-4 // when maps = 14, formula: 0.7*(bits/maps), bits = 20*elements, the error rate is 0.000067 < 1e-4
// for detailed error rate table, see http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html // for detailed error rate table, see http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html
func New(store *redis.Redis, key string, bits uint) *BloomFilter { func New(store *redis.Redis, key string, bits uint) *Filter {
return &BloomFilter{ return &Filter{
bits: bits, bits: bits,
bitSet: newRedisBitSet(store, key, bits), bitSet: newRedisBitSet(store, key, bits),
} }
} }
func (f *BloomFilter) Add(data []byte) error { // Add adds data into f.
func (f *Filter) Add(data []byte) error {
locations := f.getLocations(data) locations := f.getLocations(data)
err := f.bitSet.set(locations) return f.bitSet.set(locations)
if err != nil {
return err
}
return nil
} }
func (f *BloomFilter) Exists(data []byte) (bool, error) { // Exists checks if data is in f.
func (f *Filter) Exists(data []byte) (bool, error) {
locations := f.getLocations(data) locations := f.getLocations(data)
isSet, err := f.bitSet.check(locations) isSet, err := f.bitSet.check(locations)
if err != nil { if err != nil {
@@ -76,7 +76,7 @@ func (f *BloomFilter) Exists(data []byte) (bool, error) {
return true, nil return true, nil
} }
func (f *BloomFilter) getLocations(data []byte) []uint { func (f *Filter) getLocations(data []byte) []uint {
locations := make([]uint, maps) locations := make([]uint, maps)
for i := uint(0); i < maps; i++ { for i := uint(0); i < maps; i++ {
hashValue := hash.Hash(append(data, byte(i))) hashValue := hash.Hash(append(data, byte(i)))
@@ -127,11 +127,12 @@ func (r *redisBitSet) check(offsets []uint) (bool, error) {
return false, err return false, err
} }
if exists, ok := resp.(int64); !ok { exists, ok := resp.(int64)
if !ok {
return false, nil return false, nil
} else {
return exists == 1, nil
} }
return exists == 1, nil
} }
func (r *redisBitSet) del() error { func (r *redisBitSet) del() error {
@@ -152,7 +153,7 @@ func (r *redisBitSet) set(offsets []uint) error {
_, err = r.store.Eval(setScript, []string{r.key}, args) _, err = r.store.Eval(setScript, []string{r.key}, args)
if err == redis.Nil { if err == redis.Nil {
return nil return nil
} else {
return err
} }
return err
} }

View File

@@ -18,12 +18,14 @@ const (
timeFormat = "15:04:05" timeFormat = "15:04:05"
) )
// ErrServiceUnavailable is returned when the CB state is open // ErrServiceUnavailable is returned when the Breaker state is open.
var ErrServiceUnavailable = errors.New("circuit breaker is open") var ErrServiceUnavailable = errors.New("circuit breaker is open")
type ( type (
// Acceptable is the func to check if the error can be accepted.
Acceptable func(err error) bool Acceptable func(err error) bool
// A Breaker represents a circuit breaker.
Breaker interface { Breaker interface {
// Name returns the name of the Breaker. // Name returns the name of the Breaker.
Name() string Name() string
@@ -61,10 +63,14 @@ type (
DoWithFallbackAcceptable(req func() error, fallback func(err error) error, acceptable Acceptable) error DoWithFallbackAcceptable(req func() error, fallback func(err error) error, acceptable Acceptable) error
} }
// Option defines the method to customize a Breaker.
Option func(breaker *circuitBreaker) Option func(breaker *circuitBreaker)
// Promise interface defines the callbacks that returned by Breaker.Allow.
Promise interface { Promise interface {
// Accept tells the Breaker that the call is successful.
Accept() Accept()
// Reject tells the Breaker that the call is failed.
Reject(reason string) Reject(reason string)
} }
@@ -89,6 +95,8 @@ type (
} }
) )
// NewBreaker returns a Breaker object.
// opts can be used to customize the Breaker.
func NewBreaker(opts ...Option) Breaker { func NewBreaker(opts ...Option) Breaker {
var b circuitBreaker var b circuitBreaker
for _, opt := range opts { for _, opt := range opts {
@@ -127,6 +135,7 @@ func (cb *circuitBreaker) Name() string {
return cb.name return cb.name
} }
// WithName returns a function to set the name of a Breaker.
func WithName(name string) Option { func WithName(name string) Option {
return func(b *circuitBreaker) { return func(b *circuitBreaker) {
b.name = name b.name = name

View File

@@ -122,8 +122,7 @@ func BenchmarkGoogleBreaker(b *testing.B) {
} }
} }
type mockedPromise struct { type mockedPromise struct{}
}
func (m *mockedPromise) Accept() { func (m *mockedPromise) Accept() {
} }

View File

@@ -7,24 +7,28 @@ var (
breakers = make(map[string]Breaker) breakers = make(map[string]Breaker)
) )
// Do calls Breaker.Do on the Breaker with given name.
func Do(name string, req func() error) error { func Do(name string, req func() error) error {
return do(name, func(b Breaker) error { return do(name, func(b Breaker) error {
return b.Do(req) return b.Do(req)
}) })
} }
// DoWithAcceptable calls Breaker.DoWithAcceptable on the Breaker with given name.
func DoWithAcceptable(name string, req func() error, acceptable Acceptable) error { func DoWithAcceptable(name string, req func() error, acceptable Acceptable) error {
return do(name, func(b Breaker) error { return do(name, func(b Breaker) error {
return b.DoWithAcceptable(req, acceptable) return b.DoWithAcceptable(req, acceptable)
}) })
} }
// DoWithFallback calls Breaker.DoWithFallback on the Breaker with given name.
func DoWithFallback(name string, req func() error, fallback func(err error) error) error { func DoWithFallback(name string, req func() error, fallback func(err error) error) error {
return do(name, func(b Breaker) error { return do(name, func(b Breaker) error {
return b.DoWithFallback(req, fallback) return b.DoWithFallback(req, fallback)
}) })
} }
// DoWithFallbackAcceptable calls Breaker.DoWithFallbackAcceptable on the Breaker with given name.
func DoWithFallbackAcceptable(name string, req func() error, fallback func(err error) error, func DoWithFallbackAcceptable(name string, req func() error, fallback func(err error) error,
acceptable Acceptable) error { acceptable Acceptable) error {
return do(name, func(b Breaker) error { return do(name, func(b Breaker) error {
@@ -32,6 +36,7 @@ func DoWithFallbackAcceptable(name string, req func() error, fallback func(err e
}) })
} }
// GetBreaker returns the Breaker with the given name.
func GetBreaker(name string) Breaker { func GetBreaker(name string) Breaker {
lock.RLock() lock.RLock()
b, ok := breakers[name] b, ok := breakers[name]
@@ -51,7 +56,8 @@ func GetBreaker(name string) Breaker {
return b return b
} }
func NoBreakFor(name string) { // NoBreakerFor disables the circuit breaker for the given name.
func NoBreakerFor(name string) {
lock.Lock() lock.Lock()
breakers[name] = newNoOpBreaker() breakers[name] = newNoOpBreaker()
lock.Unlock() lock.Unlock()

View File

@@ -55,7 +55,7 @@ func TestBreakersDoWithAcceptable(t *testing.T) {
} }
func TestBreakersNoBreakerFor(t *testing.T) { func TestBreakersNoBreakerFor(t *testing.T) {
NoBreakFor("any") NoBreakerFor("any")
errDummy := errors.New("any") errDummy := errors.New("any")
for i := 0; i < 10000; i++ { for i := 0; i < 10000; i++ {
assert.Equal(t, errDummy, GetBreaker("any").Do(func() error { assert.Equal(t, errDummy, GetBreaker("any").Do(func() error {

View File

@@ -64,9 +64,9 @@ func (b *googleBreaker) doReq(req func() error, fallback func(err error) error,
if err := b.accept(); err != nil { if err := b.accept(); err != nil {
if fallback != nil { if fallback != nil {
return fallback(err) return fallback(err)
} else {
return err
} }
return err
} }
defer func() { defer func() {

View File

@@ -7,11 +7,13 @@ import (
"strings" "strings"
) )
// EnterToContinue let stdin waiting for an enter key to continue.
func EnterToContinue() { func EnterToContinue() {
fmt.Print("Press 'Enter' to continue...") fmt.Print("Press 'Enter' to continue...")
bufio.NewReader(os.Stdin).ReadBytes('\n') bufio.NewReader(os.Stdin).ReadBytes('\n')
} }
// ReadLine shows prompt to stdout and read a line from stdin.
func ReadLine(prompt string) string { func ReadLine(prompt string) string {
fmt.Print(prompt) fmt.Print(prompt)
input, _ := bufio.NewReader(os.Stdin).ReadString('\n') input, _ := bufio.NewReader(os.Stdin).ReadString('\n')

View File

@@ -10,6 +10,7 @@ import (
"github.com/tal-tech/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
) )
// ErrPaddingSize indicates bad padding size.
var ErrPaddingSize = errors.New("padding size error") var ErrPaddingSize = errors.New("padding size error")
type ecb struct { type ecb struct {
@@ -26,6 +27,7 @@ func newECB(b cipher.Block) *ecb {
type ecbEncrypter ecb type ecbEncrypter ecb
// NewECBEncrypter returns an ECB encrypter.
func NewECBEncrypter(b cipher.Block) cipher.BlockMode { func NewECBEncrypter(b cipher.Block) cipher.BlockMode {
return (*ecbEncrypter)(newECB(b)) return (*ecbEncrypter)(newECB(b))
} }
@@ -52,6 +54,7 @@ func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
type ecbDecrypter ecb type ecbDecrypter ecb
// NewECBDecrypter returns an ECB decrypter.
func NewECBDecrypter(b cipher.Block) cipher.BlockMode { func NewECBDecrypter(b cipher.Block) cipher.BlockMode {
return (*ecbDecrypter)(newECB(b)) return (*ecbDecrypter)(newECB(b))
} }
@@ -78,6 +81,7 @@ func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
} }
} }
// EcbDecrypt decrypts src with the given key.
func EcbDecrypt(key, src []byte) ([]byte, error) { func EcbDecrypt(key, src []byte) ([]byte, error) {
block, err := aes.NewCipher(key) block, err := aes.NewCipher(key)
if err != nil { if err != nil {
@@ -92,6 +96,8 @@ func EcbDecrypt(key, src []byte) ([]byte, error) {
return pkcs5Unpadding(decrypted, decrypter.BlockSize()) return pkcs5Unpadding(decrypted, decrypter.BlockSize())
} }
// EcbDecryptBase64 decrypts base64 encoded src with the given base64 encoded key.
// The returned string is also base64 encoded.
func EcbDecryptBase64(key, src string) (string, error) { func EcbDecryptBase64(key, src string) (string, error) {
keyBytes, err := getKeyBytes(key) keyBytes, err := getKeyBytes(key)
if err != nil { if err != nil {
@@ -111,6 +117,7 @@ func EcbDecryptBase64(key, src string) (string, error) {
return base64.StdEncoding.EncodeToString(decryptedBytes), nil return base64.StdEncoding.EncodeToString(decryptedBytes), nil
} }
// EcbEncrypt encrypts src with the given key.
func EcbEncrypt(key, src []byte) ([]byte, error) { func EcbEncrypt(key, src []byte) ([]byte, error) {
block, err := aes.NewCipher(key) block, err := aes.NewCipher(key)
if err != nil { if err != nil {
@@ -126,6 +133,8 @@ func EcbEncrypt(key, src []byte) ([]byte, error) {
return crypted, nil return crypted, nil
} }
// EcbEncryptBase64 encrypts base64 encoded src with the given base64 encoded key.
// The returned string is also base64 encoded.
func EcbEncryptBase64(key, src string) (string, error) { func EcbEncryptBase64(key, src string) (string, error) {
keyBytes, err := getKeyBytes(key) keyBytes, err := getKeyBytes(key)
if err != nil { if err != nil {
@@ -146,15 +155,16 @@ func EcbEncryptBase64(key, src string) (string, error) {
} }
func getKeyBytes(key string) ([]byte, error) { func getKeyBytes(key string) ([]byte, error) {
if len(key) > 32 { if len(key) <= 32 {
if keyBytes, err := base64.StdEncoding.DecodeString(key); err != nil { return []byte(key), nil
return nil, err
} else {
return keyBytes, nil
}
} }
return []byte(key), nil keyBytes, err := base64.StdEncoding.DecodeString(key)
if err != nil {
return nil, err
}
return keyBytes, nil
} }
func pkcs5Padding(ciphertext []byte, blockSize int) []byte { func pkcs5Padding(ciphertext []byte, blockSize int) []byte {

65
core/codec/aesecb_test.go Normal file
View File

@@ -0,0 +1,65 @@
package codec
import (
"encoding/base64"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAesEcb(t *testing.T) {
var (
key = []byte("q4t7w!z%C*F-JaNdRgUjXn2r5u8x/A?D")
val = []byte("hello")
badKey1 = []byte("aaaaaaaaa")
// more than 32 chars
badKey2 = []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
)
_, err := EcbEncrypt(badKey1, val)
assert.NotNil(t, err)
_, err = EcbEncrypt(badKey2, val)
assert.NotNil(t, err)
dst, err := EcbEncrypt(key, val)
assert.Nil(t, err)
_, err = EcbDecrypt(badKey1, dst)
assert.NotNil(t, err)
_, err = EcbDecrypt(badKey2, dst)
assert.NotNil(t, err)
_, err = EcbDecrypt(key, val)
// not enough block, just nil
assert.Nil(t, err)
src, err := EcbDecrypt(key, dst)
assert.Nil(t, err)
assert.Equal(t, val, src)
}
func TestAesEcbBase64(t *testing.T) {
const (
val = "hello"
badKey1 = "aaaaaaaaa"
// more than 32 chars
badKey2 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
)
key := []byte("q4t7w!z%C*F-JaNdRgUjXn2r5u8x/A?D")
b64Key := base64.StdEncoding.EncodeToString(key)
b64Val := base64.StdEncoding.EncodeToString([]byte(val))
_, err := EcbEncryptBase64(badKey1, val)
assert.NotNil(t, err)
_, err = EcbEncryptBase64(badKey2, val)
assert.NotNil(t, err)
_, err = EcbEncryptBase64(b64Key, val)
assert.NotNil(t, err)
dst, err := EcbEncryptBase64(b64Key, b64Val)
assert.Nil(t, err)
_, err = EcbDecryptBase64(badKey1, dst)
assert.NotNil(t, err)
_, err = EcbDecryptBase64(badKey2, dst)
assert.NotNil(t, err)
_, err = EcbDecryptBase64(b64Key, val)
assert.NotNil(t, err)
src, err := EcbDecryptBase64(b64Key, dst)
assert.Nil(t, err)
b, err := base64.StdEncoding.DecodeString(src)
assert.Nil(t, err)
assert.Equal(t, val, string(b))
}

View File

@@ -11,8 +11,11 @@ import (
// 2048-bit MODP Group // 2048-bit MODP Group
var ( var (
ErrInvalidPriKey = errors.New("invalid private key") // ErrInvalidPriKey indicates the invalid private key.
ErrInvalidPubKey = errors.New("invalid public key") ErrInvalidPriKey = errors.New("invalid private key")
// ErrInvalidPubKey indicates the invalid public key.
ErrInvalidPubKey = errors.New("invalid public key")
// ErrPubKeyOutOfBound indicates the public key is out of bound.
ErrPubKeyOutOfBound = errors.New("public key out of bound") ErrPubKeyOutOfBound = errors.New("public key out of bound")
p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16) p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
@@ -20,11 +23,13 @@ var (
zero = big.NewInt(0) zero = big.NewInt(0)
) )
// DhKey defines the Diffie Hellman key.
type DhKey struct { type DhKey struct {
PriKey *big.Int PriKey *big.Int
PubKey *big.Int PubKey *big.Int
} }
// ComputeKey returns a key from public key and private key.
func ComputeKey(pubKey, priKey *big.Int) (*big.Int, error) { func ComputeKey(pubKey, priKey *big.Int) (*big.Int, error) {
if pubKey == nil { if pubKey == nil {
return nil, ErrInvalidPubKey return nil, ErrInvalidPubKey
@@ -41,6 +46,7 @@ func ComputeKey(pubKey, priKey *big.Int) (*big.Int, error) {
return new(big.Int).Exp(pubKey, priKey, p), nil return new(big.Int).Exp(pubKey, priKey, p), nil
} }
// GenerateKey returns a Diffie Hellman key.
func GenerateKey() (*DhKey, error) { func GenerateKey() (*DhKey, error) {
var err error var err error
var x *big.Int var x *big.Int
@@ -63,10 +69,12 @@ func GenerateKey() (*DhKey, error) {
return key, nil return key, nil
} }
// NewPublicKey returns a public key from the given bytes.
func NewPublicKey(bs []byte) *big.Int { func NewPublicKey(bs []byte) *big.Int {
return new(big.Int).SetBytes(bs) return new(big.Int).SetBytes(bs)
} }
// Bytes returns public key bytes.
func (k *DhKey) Bytes() []byte { func (k *DhKey) Bytes() []byte {
if k.PubKey == nil { if k.PubKey == nil {
return nil return nil

View File

@@ -8,6 +8,7 @@ import (
const unzipLimit = 100 * 1024 * 1024 // 100MB const unzipLimit = 100 * 1024 * 1024 // 100MB
// Gzip compresses bs.
func Gzip(bs []byte) []byte { func Gzip(bs []byte) []byte {
var b bytes.Buffer var b bytes.Buffer
@@ -18,6 +19,7 @@ func Gzip(bs []byte) []byte {
return b.Bytes() return b.Bytes()
} }
// Gunzip uncompresses bs.
func Gunzip(bs []byte) ([]byte, error) { func Gunzip(bs []byte) ([]byte, error) {
r, err := gzip.NewReader(bytes.NewBuffer(bs)) r, err := gzip.NewReader(bytes.NewBuffer(bs))
if err != nil { if err != nil {

View File

@@ -7,12 +7,14 @@ import (
"io" "io"
) )
// Hmac returns HMAC bytes for body with the given key.
func Hmac(key []byte, body string) []byte { func Hmac(key []byte, body string) []byte {
h := hmac.New(sha256.New, key) h := hmac.New(sha256.New, key)
io.WriteString(h, body) io.WriteString(h, body)
return h.Sum(nil) return h.Sum(nil)
} }
// HmacBase64 returns the base64 encoded string of HMAC for body with the given key.
func HmacBase64(key []byte, body string) string { func HmacBase64(key []byte, body string) string {
return base64.StdEncoding.EncodeToString(Hmac(key, body)) return base64.StdEncoding.EncodeToString(Hmac(key, body))
} }

View File

@@ -11,17 +11,22 @@ import (
) )
var ( var (
// ErrPrivateKey indicates the invalid private key.
ErrPrivateKey = errors.New("private key error") ErrPrivateKey = errors.New("private key error")
ErrPublicKey = errors.New("failed to parse PEM block containing the public key") // ErrPublicKey indicates the invalid public key.
ErrNotRsaKey = errors.New("key type is not RSA") ErrPublicKey = errors.New("failed to parse PEM block containing the public key")
// ErrNotRsaKey indicates the invalid RSA key.
ErrNotRsaKey = errors.New("key type is not RSA")
) )
type ( type (
// RsaDecrypter represents a RSA decrypter.
RsaDecrypter interface { RsaDecrypter interface {
Decrypt(input []byte) ([]byte, error) Decrypt(input []byte) ([]byte, error)
DecryptBase64(input string) ([]byte, error) DecryptBase64(input string) ([]byte, error)
} }
// RsaEncrypter represents a RSA encrypter.
RsaEncrypter interface { RsaEncrypter interface {
Encrypt(input []byte) ([]byte, error) Encrypt(input []byte) ([]byte, error)
} }
@@ -41,6 +46,7 @@ type (
} }
) )
// NewRsaDecrypter returns a RsaDecrypter with the given file.
func NewRsaDecrypter(file string) (RsaDecrypter, error) { func NewRsaDecrypter(file string) (RsaDecrypter, error) {
content, err := ioutil.ReadFile(file) content, err := ioutil.ReadFile(file)
if err != nil { if err != nil {
@@ -84,6 +90,7 @@ func (r *rsaDecrypter) DecryptBase64(input string) ([]byte, error) {
return r.Decrypt(base64Decoded) return r.Decrypt(base64Decoded)
} }
// NewRsaEncrypter returns a RsaEncrypter with the given key.
func NewRsaEncrypter(key []byte) (RsaEncrypter, error) { func NewRsaEncrypter(key []byte) (RsaEncrypter, error) {
block, _ := pem.Decode(key) block, _ := pem.Decode(key)
if block == nil { if block == nil {

View File

@@ -23,8 +23,10 @@ const (
var emptyLruCache = emptyLru{} var emptyLruCache = emptyLru{}
type ( type (
// CacheOption defines the method to customize a Cache.
CacheOption func(cache *Cache) CacheOption func(cache *Cache)
// A Cache object is a in-memory cache.
Cache struct { Cache struct {
name string name string
lock sync.Mutex lock sync.Mutex
@@ -38,6 +40,7 @@ type (
} }
) )
// NewCache returns a Cache with given expire.
func NewCache(expire time.Duration, opts ...CacheOption) (*Cache, error) { func NewCache(expire time.Duration, opts ...CacheOption) (*Cache, error) {
cache := &Cache{ cache := &Cache{
data: make(map[string]interface{}), data: make(map[string]interface{}),
@@ -72,6 +75,7 @@ func NewCache(expire time.Duration, opts ...CacheOption) (*Cache, error) {
return cache, nil return cache, nil
} }
// Del deletes the item with the given key from c.
func (c *Cache) Del(key string) { func (c *Cache) Del(key string) {
c.lock.Lock() c.lock.Lock()
delete(c.data, key) delete(c.data, key)
@@ -80,6 +84,7 @@ func (c *Cache) Del(key string) {
c.timingWheel.RemoveTimer(key) c.timingWheel.RemoveTimer(key)
} }
// Get returns the item with the given key from c.
func (c *Cache) Get(key string) (interface{}, bool) { func (c *Cache) Get(key string) (interface{}, bool) {
value, ok := c.doGet(key) value, ok := c.doGet(key)
if ok { if ok {
@@ -91,6 +96,7 @@ func (c *Cache) Get(key string) (interface{}, bool) {
return value, ok return value, ok
} }
// Set sets value into c with key.
func (c *Cache) Set(key string, value interface{}) { func (c *Cache) Set(key string, value interface{}) {
c.lock.Lock() c.lock.Lock()
_, ok := c.data[key] _, ok := c.data[key]
@@ -106,6 +112,9 @@ func (c *Cache) Set(key string, value interface{}) {
} }
} }
// Take returns the item with the given key.
// If the item is in c, return it directly.
// If not, use fetch method to get the item, set into c and return it.
func (c *Cache) Take(key string, fetch func() (interface{}, error)) (interface{}, error) { func (c *Cache) Take(key string, fetch func() (interface{}, error)) (interface{}, error) {
if val, ok := c.doGet(key); ok { if val, ok := c.doGet(key); ok {
c.stats.IncrementHit() c.stats.IncrementHit()
@@ -136,11 +145,10 @@ func (c *Cache) Take(key string, fetch func() (interface{}, error)) (interface{}
if fresh { if fresh {
c.stats.IncrementMiss() c.stats.IncrementMiss()
return val, nil return val, nil
} else {
// got the result from previous ongoing query
c.stats.IncrementHit()
} }
// got the result from previous ongoing query
c.stats.IncrementHit()
return val, nil return val, nil
} }
@@ -168,6 +176,7 @@ func (c *Cache) size() int {
return len(c.data) return len(c.data)
} }
// WithLimit customizes a Cache with items up to limit.
func WithLimit(limit int) CacheOption { func WithLimit(limit int) CacheOption {
return func(cache *Cache) { return func(cache *Cache) {
if limit > 0 { if limit > 0 {
@@ -176,6 +185,7 @@ func WithLimit(limit int) CacheOption {
} }
} }
// WithName customizes a Cache with the given name.
func WithName(name string) CacheOption { func WithName(name string) CacheOption {
return func(cache *Cache) { return func(cache *Cache) {
cache.name = name cache.name = name

View File

@@ -2,6 +2,7 @@ package collection
import "sync" import "sync"
// A Queue is a FIFO queue.
type Queue struct { type Queue struct {
lock sync.Mutex lock sync.Mutex
elements []interface{} elements []interface{}
@@ -11,6 +12,7 @@ type Queue struct {
count int count int
} }
// NewQueue returns a Queue object.
func NewQueue(size int) *Queue { func NewQueue(size int) *Queue {
return &Queue{ return &Queue{
elements: make([]interface{}, size), elements: make([]interface{}, size),
@@ -18,6 +20,7 @@ func NewQueue(size int) *Queue {
} }
} }
// Empty checks if q is empty.
func (q *Queue) Empty() bool { func (q *Queue) Empty() bool {
q.lock.Lock() q.lock.Lock()
empty := q.count == 0 empty := q.count == 0
@@ -26,6 +29,7 @@ func (q *Queue) Empty() bool {
return empty return empty
} }
// Put puts element into q at the last position.
func (q *Queue) Put(element interface{}) { func (q *Queue) Put(element interface{}) {
q.lock.Lock() q.lock.Lock()
defer q.lock.Unlock() defer q.lock.Unlock()
@@ -44,6 +48,7 @@ func (q *Queue) Put(element interface{}) {
q.count++ q.count++
} }
// Take takes the first element out of q if not empty.
func (q *Queue) Take() (interface{}, bool) { func (q *Queue) Take() (interface{}, bool) {
q.lock.Lock() q.lock.Lock()
defer q.lock.Unlock() defer q.lock.Unlock()

View File

@@ -1,10 +1,15 @@
package collection package collection
import "sync"
// A Ring can be used as fixed size ring.
type Ring struct { type Ring struct {
elements []interface{} elements []interface{}
index int index int
lock sync.Mutex
} }
// NewRing returns a Ring object with the given size n.
func NewRing(n int) *Ring { func NewRing(n int) *Ring {
if n < 1 { if n < 1 {
panic("n should be greater than 0") panic("n should be greater than 0")
@@ -15,12 +20,20 @@ func NewRing(n int) *Ring {
} }
} }
// Add adds v into r.
func (r *Ring) Add(v interface{}) { func (r *Ring) Add(v interface{}) {
r.lock.Lock()
defer r.lock.Unlock()
r.elements[r.index%len(r.elements)] = v r.elements[r.index%len(r.elements)] = v
r.index++ r.index++
} }
// Take takes all items from r.
func (r *Ring) Take() []interface{} { func (r *Ring) Take() []interface{} {
r.lock.Lock()
defer r.lock.Unlock()
var size int var size int
var start int var start int
if r.index > len(r.elements) { if r.index > len(r.elements) {

View File

@@ -1,6 +1,7 @@
package collection package collection
import ( import (
"sync"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -29,3 +30,30 @@ func TestRingMore(t *testing.T) {
elements := ring.Take() elements := ring.Take()
assert.ElementsMatch(t, []interface{}{6, 7, 8, 9, 10}, elements) assert.ElementsMatch(t, []interface{}{6, 7, 8, 9, 10}, elements)
} }
func TestRingAdd(t *testing.T) {
ring := NewRing(5051)
wg := sync.WaitGroup{}
for i := 1; i <= 100; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
for j := 1; j <= i; j++ {
ring.Add(i)
}
}(i)
}
wg.Wait()
assert.Equal(t, 5050, len(ring.Take()))
}
func BenchmarkRingAdd(b *testing.B) {
ring := NewRing(500)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := 0; i < b.N; i++ {
ring.Add(i)
}
}
})
}

View File

@@ -8,8 +8,10 @@ import (
) )
type ( type (
// RollingWindowOption let callers customize the RollingWindow.
RollingWindowOption func(rollingWindow *RollingWindow) RollingWindowOption func(rollingWindow *RollingWindow)
// RollingWindow defines a rolling window to calculate the events in buckets with time interval.
RollingWindow struct { RollingWindow struct {
lock sync.RWMutex lock sync.RWMutex
size int size int
@@ -17,10 +19,12 @@ type (
interval time.Duration interval time.Duration
offset int offset int
ignoreCurrent bool ignoreCurrent bool
lastTime time.Duration lastTime time.Duration // start time of the last bucket
} }
) )
// NewRollingWindow returns a RollingWindow that with size buckets and time interval,
// use opts to customize the RollingWindow.
func NewRollingWindow(size int, interval time.Duration, opts ...RollingWindowOption) *RollingWindow { func NewRollingWindow(size int, interval time.Duration, opts ...RollingWindowOption) *RollingWindow {
if size < 1 { if size < 1 {
panic("size must be greater than 0") panic("size must be greater than 0")
@@ -38,6 +42,7 @@ func NewRollingWindow(size int, interval time.Duration, opts ...RollingWindowOpt
return w return w
} }
// Add adds value to current bucket.
func (rw *RollingWindow) Add(v float64) { func (rw *RollingWindow) Add(v float64) {
rw.lock.Lock() rw.lock.Lock()
defer rw.lock.Unlock() defer rw.lock.Unlock()
@@ -45,6 +50,7 @@ func (rw *RollingWindow) Add(v float64) {
rw.win.add(rw.offset, v) rw.win.add(rw.offset, v)
} }
// Reduce runs fn on all buckets, ignore current bucket if ignoreCurrent was set.
func (rw *RollingWindow) Reduce(fn func(b *Bucket)) { func (rw *RollingWindow) Reduce(fn func(b *Bucket)) {
rw.lock.RLock() rw.lock.RLock()
defer rw.lock.RUnlock() defer rw.lock.RUnlock()
@@ -67,9 +73,9 @@ func (rw *RollingWindow) span() int {
offset := int(timex.Since(rw.lastTime) / rw.interval) offset := int(timex.Since(rw.lastTime) / rw.interval)
if 0 <= offset && offset < rw.size { if 0 <= offset && offset < rw.size {
return offset return offset
} else {
return rw.size
} }
return rw.size
} }
func (rw *RollingWindow) updateOffset() { func (rw *RollingWindow) updateOffset() {
@@ -79,26 +85,18 @@ func (rw *RollingWindow) updateOffset() {
} }
offset := rw.offset offset := rw.offset
start := offset + 1
steps := start + span
var remainder int
if steps > rw.size {
remainder = steps - rw.size
steps = rw.size
}
// reset expired buckets // reset expired buckets
for i := start; i < steps; i++ { for i := 0; i < span; i++ {
rw.win.resetBucket(i) rw.win.resetBucket((offset + i + 1) % rw.size)
}
for i := 0; i < remainder; i++ {
rw.win.resetBucket(i)
} }
rw.offset = (offset + span) % rw.size rw.offset = (offset + span) % rw.size
rw.lastTime = timex.Now() now := timex.Now()
// align to interval time boundary
rw.lastTime = now - (now-rw.lastTime)%rw.interval
} }
// Bucket defines the bucket that holds sum and num of additions.
type Bucket struct { type Bucket struct {
Sum float64 Sum float64
Count int64 Count int64
@@ -144,6 +142,7 @@ func (w *window) resetBucket(offset int) {
w.buckets[offset%w.size].reset() w.buckets[offset%w.size].reset()
} }
// IgnoreCurrentBucket lets the Reduce call ignore current bucket.
func IgnoreCurrentBucket() RollingWindowOption { func IgnoreCurrentBucket() RollingWindowOption {
return func(w *RollingWindow) { return func(w *RollingWindow) {
w.ignoreCurrent = true w.ignoreCurrent = true

View File

@@ -105,10 +105,41 @@ func TestRollingWindowReduce(t *testing.T) {
} }
} }
func TestRollingWindowBucketTimeBoundary(t *testing.T) {
const size = 3
interval := time.Millisecond * 30
r := NewRollingWindow(size, interval)
listBuckets := func() []float64 {
var buckets []float64
r.Reduce(func(b *Bucket) {
buckets = append(buckets, b.Sum)
})
return buckets
}
assert.Equal(t, []float64{0, 0, 0}, listBuckets())
r.Add(1)
assert.Equal(t, []float64{0, 0, 1}, listBuckets())
time.Sleep(time.Millisecond * 45)
r.Add(2)
r.Add(3)
assert.Equal(t, []float64{0, 1, 5}, listBuckets())
// sleep time should be less than interval, and make the bucket change happen
time.Sleep(time.Millisecond * 20)
r.Add(4)
r.Add(5)
r.Add(6)
assert.Equal(t, []float64{1, 5, 15}, listBuckets())
time.Sleep(time.Millisecond * 100)
r.Add(7)
r.Add(8)
r.Add(9)
assert.Equal(t, []float64{0, 0, 24}, listBuckets())
}
func TestRollingWindowDataRace(t *testing.T) { func TestRollingWindowDataRace(t *testing.T) {
const size = 3 const size = 3
r := NewRollingWindow(size, duration) r := NewRollingWindow(size, duration)
var stop = make(chan bool) stop := make(chan bool)
go func() { go func() {
for { for {
select { select {

View File

@@ -18,6 +18,7 @@ type SafeMap struct {
dirtyNew map[interface{}]interface{} dirtyNew map[interface{}]interface{}
} }
// NewSafeMap returns a SafeMap.
func NewSafeMap() *SafeMap { func NewSafeMap() *SafeMap {
return &SafeMap{ return &SafeMap{
dirtyOld: make(map[interface{}]interface{}), dirtyOld: make(map[interface{}]interface{}),
@@ -25,6 +26,7 @@ func NewSafeMap() *SafeMap {
} }
} }
// Del deletes the value with the given key from m.
func (m *SafeMap) Del(key interface{}) { func (m *SafeMap) Del(key interface{}) {
m.lock.Lock() m.lock.Lock()
if _, ok := m.dirtyOld[key]; ok { if _, ok := m.dirtyOld[key]; ok {
@@ -53,18 +55,20 @@ func (m *SafeMap) Del(key interface{}) {
m.lock.Unlock() m.lock.Unlock()
} }
// Get gets the value with the given key from m.
func (m *SafeMap) Get(key interface{}) (interface{}, bool) { func (m *SafeMap) Get(key interface{}) (interface{}, bool) {
m.lock.RLock() m.lock.RLock()
defer m.lock.RUnlock() defer m.lock.RUnlock()
if val, ok := m.dirtyOld[key]; ok { if val, ok := m.dirtyOld[key]; ok {
return val, true return val, true
} else {
val, ok := m.dirtyNew[key]
return val, ok
} }
val, ok := m.dirtyNew[key]
return val, ok
} }
// Set sets the value into m with the given key.
func (m *SafeMap) Set(key, value interface{}) { func (m *SafeMap) Set(key, value interface{}) {
m.lock.Lock() m.lock.Lock()
if m.deletionOld <= maxDeletion { if m.deletionOld <= maxDeletion {
@@ -83,6 +87,7 @@ func (m *SafeMap) Set(key, value interface{}) {
m.lock.Unlock() m.lock.Unlock()
} }
// Size returns the size of m.
func (m *SafeMap) Size() int { func (m *SafeMap) Size() int {
m.lock.RLock() m.lock.RLock()
size := len(m.dirtyOld) + len(m.dirtyNew) size := len(m.dirtyOld) + len(m.dirtyNew)

View File

@@ -21,6 +21,7 @@ type Set struct {
tp int tp int
} }
// NewSet returns a managed Set, can only put the values with the same type.
func NewSet() *Set { func NewSet() *Set {
return &Set{ return &Set{
data: make(map[interface{}]lang.PlaceholderType), data: make(map[interface{}]lang.PlaceholderType),
@@ -28,6 +29,7 @@ func NewSet() *Set {
} }
} }
// NewUnmanagedSet returns a unmanaged Set, which can put values with different types.
func NewUnmanagedSet() *Set { func NewUnmanagedSet() *Set {
return &Set{ return &Set{
data: make(map[interface{}]lang.PlaceholderType), data: make(map[interface{}]lang.PlaceholderType),
@@ -35,42 +37,49 @@ func NewUnmanagedSet() *Set {
} }
} }
// Add adds i into s.
func (s *Set) Add(i ...interface{}) { func (s *Set) Add(i ...interface{}) {
for _, each := range i { for _, each := range i {
s.add(each) s.add(each)
} }
} }
// AddInt adds int values ii into s.
func (s *Set) AddInt(ii ...int) { func (s *Set) AddInt(ii ...int) {
for _, each := range ii { for _, each := range ii {
s.add(each) s.add(each)
} }
} }
// AddInt64 adds int64 values ii into s.
func (s *Set) AddInt64(ii ...int64) { func (s *Set) AddInt64(ii ...int64) {
for _, each := range ii { for _, each := range ii {
s.add(each) s.add(each)
} }
} }
// AddUint adds uint values ii into s.
func (s *Set) AddUint(ii ...uint) { func (s *Set) AddUint(ii ...uint) {
for _, each := range ii { for _, each := range ii {
s.add(each) s.add(each)
} }
} }
// AddUint64 adds uint64 values ii into s.
func (s *Set) AddUint64(ii ...uint64) { func (s *Set) AddUint64(ii ...uint64) {
for _, each := range ii { for _, each := range ii {
s.add(each) s.add(each)
} }
} }
// AddStr adds string values ss into s.
func (s *Set) AddStr(ss ...string) { func (s *Set) AddStr(ss ...string) {
for _, each := range ss { for _, each := range ss {
s.add(each) s.add(each)
} }
} }
// Contains checks if i is in s.
func (s *Set) Contains(i interface{}) bool { func (s *Set) Contains(i interface{}) bool {
if len(s.data) == 0 { if len(s.data) == 0 {
return false return false
@@ -81,6 +90,7 @@ func (s *Set) Contains(i interface{}) bool {
return ok return ok
} }
// Keys returns the keys in s.
func (s *Set) Keys() []interface{} { func (s *Set) Keys() []interface{} {
var keys []interface{} var keys []interface{}
@@ -91,6 +101,7 @@ func (s *Set) Keys() []interface{} {
return keys return keys
} }
// KeysInt returns the int keys in s.
func (s *Set) KeysInt() []int { func (s *Set) KeysInt() []int {
var keys []int var keys []int
@@ -105,6 +116,7 @@ func (s *Set) KeysInt() []int {
return keys return keys
} }
// KeysInt64 returns int64 keys in s.
func (s *Set) KeysInt64() []int64 { func (s *Set) KeysInt64() []int64 {
var keys []int64 var keys []int64
@@ -119,6 +131,7 @@ func (s *Set) KeysInt64() []int64 {
return keys return keys
} }
// KeysUint returns uint keys in s.
func (s *Set) KeysUint() []uint { func (s *Set) KeysUint() []uint {
var keys []uint var keys []uint
@@ -133,6 +146,7 @@ func (s *Set) KeysUint() []uint {
return keys return keys
} }
// KeysUint64 returns uint64 keys in s.
func (s *Set) KeysUint64() []uint64 { func (s *Set) KeysUint64() []uint64 {
var keys []uint64 var keys []uint64
@@ -147,6 +161,7 @@ func (s *Set) KeysUint64() []uint64 {
return keys return keys
} }
// KeysStr returns string keys in s.
func (s *Set) KeysStr() []string { func (s *Set) KeysStr() []string {
var keys []string var keys []string
@@ -161,11 +176,13 @@ func (s *Set) KeysStr() []string {
return keys return keys
} }
// Remove removes i from s.
func (s *Set) Remove(i interface{}) { func (s *Set) Remove(i interface{}) {
s.validate(i) s.validate(i)
delete(s.data, i) delete(s.data, i)
} }
// Count returns the number of items in s.
func (s *Set) Count() int { func (s *Set) Count() int {
return len(s.data) return len(s.data)
} }

View File

@@ -13,8 +13,10 @@ import (
const drainWorkers = 8 const drainWorkers = 8
type ( type (
// Execute defines the method to execute the task.
Execute func(key, value interface{}) Execute func(key, value interface{})
// A TimingWheel is a timing wheel object to schedule tasks.
TimingWheel struct { TimingWheel struct {
interval time.Duration interval time.Duration
ticker timex.Ticker ticker timex.Ticker
@@ -54,6 +56,7 @@ type (
} }
) )
// NewTimingWheel returns a TimingWheel.
func NewTimingWheel(interval time.Duration, numSlots int, execute Execute) (*TimingWheel, error) { func NewTimingWheel(interval time.Duration, numSlots int, execute Execute) (*TimingWheel, error) {
if interval <= 0 || numSlots <= 0 || execute == nil { if interval <= 0 || numSlots <= 0 || execute == nil {
return nil, fmt.Errorf("interval: %v, slots: %d, execute: %p", interval, numSlots, execute) return nil, fmt.Errorf("interval: %v, slots: %d, execute: %p", interval, numSlots, execute)
@@ -85,10 +88,12 @@ func newTimingWheelWithClock(interval time.Duration, numSlots int, execute Execu
return tw, nil return tw, nil
} }
// Drain drains all items and executes them.
func (tw *TimingWheel) Drain(fn func(key, value interface{})) { func (tw *TimingWheel) Drain(fn func(key, value interface{})) {
tw.drainChannel <- fn tw.drainChannel <- fn
} }
// MoveTimer moves the task with the given key to the given delay.
func (tw *TimingWheel) MoveTimer(key interface{}, delay time.Duration) { func (tw *TimingWheel) MoveTimer(key interface{}, delay time.Duration) {
if delay <= 0 || key == nil { if delay <= 0 || key == nil {
return return
@@ -100,6 +105,7 @@ func (tw *TimingWheel) MoveTimer(key interface{}, delay time.Duration) {
} }
} }
// RemoveTimer removes the task with the given key.
func (tw *TimingWheel) RemoveTimer(key interface{}) { func (tw *TimingWheel) RemoveTimer(key interface{}) {
if key == nil { if key == nil {
return return
@@ -108,6 +114,7 @@ func (tw *TimingWheel) RemoveTimer(key interface{}) {
tw.removeChannel <- key tw.removeChannel <- key
} }
// SetTimer sets the task value with the given key to the delay.
func (tw *TimingWheel) SetTimer(key, value interface{}, delay time.Duration) { func (tw *TimingWheel) SetTimer(key, value interface{}, delay time.Duration) {
if delay <= 0 || key == nil { if delay <= 0 || key == nil {
return return
@@ -122,6 +129,7 @@ func (tw *TimingWheel) SetTimer(key, value interface{}, delay time.Duration) {
} }
} }
// Stop stops tw.
func (tw *TimingWheel) Stop() { func (tw *TimingWheel) Stop() {
close(tw.stopChannel) close(tw.stopChannel)
} }

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"os"
"path" "path"
"github.com/tal-tech/go-zero/core/mapping" "github.com/tal-tech/go-zero/core/mapping"
@@ -15,26 +16,43 @@ var loaders = map[string]func([]byte, interface{}) error{
".yml": LoadConfigFromYamlBytes, ".yml": LoadConfigFromYamlBytes,
} }
func LoadConfig(file string, v interface{}) error { // LoadConfig loads config into v from file, .json, .yaml and .yml are acceptable.
if content, err := ioutil.ReadFile(file); err != nil { func LoadConfig(file string, v interface{}, opts ...Option) error {
content, err := ioutil.ReadFile(file)
if err != nil {
return err return err
} else if loader, ok := loaders[path.Ext(file)]; ok {
return loader(content, v)
} else {
return fmt.Errorf("unrecoginized file type: %s", file)
} }
loader, ok := loaders[path.Ext(file)]
if !ok {
return fmt.Errorf("unrecognized file type: %s", file)
}
var opt options
for _, o := range opts {
o(&opt)
}
if opt.env {
return loader([]byte(os.ExpandEnv(string(content))), v)
}
return loader(content, v)
} }
// LoadConfigFromJsonBytes loads config into v from content json bytes.
func LoadConfigFromJsonBytes(content []byte, v interface{}) error { func LoadConfigFromJsonBytes(content []byte, v interface{}) error {
return mapping.UnmarshalJsonBytes(content, v) return mapping.UnmarshalJsonBytes(content, v)
} }
// LoadConfigFromYamlBytes loads config into v from content yaml bytes.
func LoadConfigFromYamlBytes(content []byte, v interface{}) error { func LoadConfigFromYamlBytes(content []byte, v interface{}) error {
return mapping.UnmarshalYamlBytes(content, v) return mapping.UnmarshalYamlBytes(content, v)
} }
func MustLoad(path string, v interface{}) { // MustLoad loads config into v from path, exits on error.
if err := LoadConfig(path, v); err != nil { func MustLoad(path string, v interface{}, opts ...Option) {
if err := LoadConfig(path, v, opts...); err != nil {
log.Fatalf("error: config file %s, %s", path, err.Error()) log.Fatalf("error: config file %s, %s", path, err.Error())
} }
} }

View File

@@ -6,9 +6,21 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/fs"
"github.com/tal-tech/go-zero/core/hash" "github.com/tal-tech/go-zero/core/hash"
) )
func TestLoadConfig_notExists(t *testing.T) {
assert.NotNil(t, LoadConfig("not_a_file", nil))
}
func TestLoadConfig_notRecogFile(t *testing.T) {
filename, err := fs.TempFilenameWithText("hello")
assert.Nil(t, err)
defer os.Remove(filename)
assert.NotNil(t, LoadConfig(filename, nil))
}
func TestConfigJson(t *testing.T) { func TestConfigJson(t *testing.T) {
tests := []string{ tests := []string{
".json", ".json",
@@ -17,13 +29,15 @@ func TestConfigJson(t *testing.T) {
} }
text := `{ text := `{
"a": "foo", "a": "foo",
"b": 1 "b": 1,
"c": "${FOO}",
"d": "abcd!@#$112"
}` }`
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test, func(t *testing.T) { t.Run(test, func(t *testing.T) {
t.Parallel() os.Setenv("FOO", "2")
defer os.Unsetenv("FOO")
tmpfile, err := createTempFile(test, text) tmpfile, err := createTempFile(test, text)
assert.Nil(t, err) assert.Nil(t, err)
defer os.Remove(tmpfile) defer os.Remove(tmpfile)
@@ -31,10 +45,50 @@ func TestConfigJson(t *testing.T) {
var val struct { var val struct {
A string `json:"a"` A string `json:"a"`
B int `json:"b"` B int `json:"b"`
C string `json:"c"`
D string `json:"d"`
} }
MustLoad(tmpfile, &val) MustLoad(tmpfile, &val)
assert.Equal(t, "foo", val.A) assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B) assert.Equal(t, 1, val.B)
assert.Equal(t, "${FOO}", val.C)
assert.Equal(t, "abcd!@#$112", val.D)
})
}
}
func TestConfigJsonEnv(t *testing.T) {
tests := []string{
".json",
".yaml",
".yml",
}
text := `{
"a": "foo",
"b": 1,
"c": "${FOO}",
"d": "abcd!@#$a12 3"
}`
for _, test := range tests {
test := test
t.Run(test, func(t *testing.T) {
os.Setenv("FOO", "2")
defer os.Unsetenv("FOO")
tmpfile, err := createTempFile(test, text)
assert.Nil(t, err)
defer os.Remove(tmpfile)
var val struct {
A string `json:"a"`
B int `json:"b"`
C string `json:"c"`
D string `json:"d"`
}
MustLoad(tmpfile, &val, UseEnv())
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
assert.Equal(t, "2", val.C)
assert.Equal(t, "abcd!@# 3", val.D)
}) })
} }
} }

17
core/conf/options.go Normal file
View File

@@ -0,0 +1,17 @@
package conf
type (
// Option defines the method to customize the config options.
Option func(opt *options)
options struct {
env bool
}
)
// UseEnv customizes the config to use environment variables.
func UseEnv() Option {
return func(opt *options) {
opt.env = true
}
}

View File

@@ -2,6 +2,7 @@ package conf
import ( import (
"fmt" "fmt"
"os"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -30,14 +31,19 @@ type mapBasedProperties struct {
lock sync.RWMutex lock sync.RWMutex
} }
// Loads the properties into a properties configuration instance. // LoadProperties loads the properties into a properties configuration instance.
// Returns an error that indicates if there was a problem loading the configuration. // Returns an error that indicates if there was a problem loading the configuration.
func LoadProperties(filename string) (Properties, error) { func LoadProperties(filename string, opts ...Option) (Properties, error) {
lines, err := iox.ReadTextLines(filename, iox.WithoutBlank(), iox.OmitWithPrefix("#")) lines, err := iox.ReadTextLines(filename, iox.WithoutBlank(), iox.OmitWithPrefix("#"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
var opt options
for _, o := range opts {
o(&opt)
}
raw := make(map[string]string) raw := make(map[string]string)
for i := range lines { for i := range lines {
pair := strings.Split(lines[i], "=") pair := strings.Split(lines[i], "=")
@@ -50,7 +56,11 @@ func LoadProperties(filename string) (Properties, error) {
key := strings.TrimSpace(pair[0]) key := strings.TrimSpace(pair[0])
value := strings.TrimSpace(pair[1]) value := strings.TrimSpace(pair[1])
raw[key] = value if opt.env {
raw[key] = os.ExpandEnv(value)
} else {
raw[key] = value
}
} }
return &mapBasedProperties{ return &mapBasedProperties{
@@ -87,7 +97,7 @@ func (config *mapBasedProperties) SetInt(key string, value int) {
config.lock.Unlock() config.lock.Unlock()
} }
// Dumps the configuration internal map into a string. // ToString dumps the configuration internal map into a string.
func (config *mapBasedProperties) ToString() string { func (config *mapBasedProperties) ToString() string {
config.lock.RLock() config.lock.RLock()
ret := fmt.Sprintf("%s", config.properties) ret := fmt.Sprintf("%s", config.properties)
@@ -96,12 +106,12 @@ func (config *mapBasedProperties) ToString() string {
return ret return ret
} }
// Returns the error message. // Error returns the error message.
func (configError *PropertyError) Error() string { func (configError *PropertyError) Error() string {
return configError.message return configError.message
} }
// Builds a new properties configuration structure // NewProperties builds a new properties configuration structure.
func NewProperties() Properties { func NewProperties() Properties {
return &mapBasedProperties{ return &mapBasedProperties{
properties: make(map[string]string), properties: make(map[string]string),

View File

@@ -24,6 +24,53 @@ func TestProperties(t *testing.T) {
assert.Equal(t, "test", props.GetString("app.name")) assert.Equal(t, "test", props.GetString("app.name"))
assert.Equal(t, "app", props.GetString("app.program")) assert.Equal(t, "app", props.GetString("app.program"))
assert.Equal(t, 5, props.GetInt("app.threads")) assert.Equal(t, 5, props.GetInt("app.threads"))
val := props.ToString()
assert.Contains(t, val, "app.name")
assert.Contains(t, val, "app.program")
assert.Contains(t, val, "app.threads")
}
func TestPropertiesEnv(t *testing.T) {
text := `app.name = test
app.program=app
app.env1 = ${FOO}
app.env2 = $none
# this is comment
app.threads = 5`
tmpfile, err := fs.TempFilenameWithText(text)
assert.Nil(t, err)
defer os.Remove(tmpfile)
os.Setenv("FOO", "2")
defer os.Unsetenv("FOO")
props, err := LoadProperties(tmpfile, UseEnv())
assert.Nil(t, err)
assert.Equal(t, "test", props.GetString("app.name"))
assert.Equal(t, "app", props.GetString("app.program"))
assert.Equal(t, 5, props.GetInt("app.threads"))
assert.Equal(t, "2", props.GetString("app.env1"))
assert.Equal(t, "", props.GetString("app.env2"))
val := props.ToString()
assert.Contains(t, val, "app.name")
assert.Contains(t, val, "app.program")
assert.Contains(t, val, "app.threads")
assert.Contains(t, val, "app.env1")
assert.Contains(t, val, "app.env2")
}
func TestLoadProperties_badContent(t *testing.T) {
filename, err := fs.TempFilenameWithText("hello")
assert.Nil(t, err)
defer os.Remove(filename)
_, err = LoadProperties(filename)
assert.NotNil(t, err)
assert.True(t, len(err.Error()) > 0)
} }
func TestSetString(t *testing.T) { func TestSetString(t *testing.T) {

View File

@@ -1,17 +0,0 @@
package contextx
import (
"context"
"time"
)
func ShrinkDeadline(ctx context.Context, timeout time.Duration) (context.Context, func()) {
if deadline, ok := ctx.Deadline(); ok {
leftTime := time.Until(deadline)
if leftTime < timeout {
timeout = leftTime
}
}
return context.WithDeadline(ctx, time.Now().Add(timeout))
}

View File

@@ -1,31 +0,0 @@
package contextx
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestShrinkDeadlineLess(t *testing.T) {
deadline := time.Now().Add(time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
ctx, cancel = ShrinkDeadline(ctx, time.Minute)
defer cancel()
dl, ok := ctx.Deadline()
assert.True(t, ok)
assert.Equal(t, deadline, dl)
}
func TestShrinkDeadlineMore(t *testing.T) {
deadline := time.Now().Add(time.Minute)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
ctx, cancel = ShrinkDeadline(ctx, time.Second)
defer cancel()
dl, ok := ctx.Deadline()
assert.True(t, ok)
assert.True(t, dl.Before(deadline))
}

View File

@@ -19,6 +19,7 @@ func (cv contextValuer) Value(key string) (interface{}, bool) {
return v, v != nil return v, v != nil
} }
// For unmarshals ctx into v.
func For(ctx context.Context, v interface{}) error { func For(ctx context.Context, v interface{}) error {
return unmarshaler.UnmarshalValuer(contextValuer{ return unmarshaler.UnmarshalValuer(contextValuer{
Context: ctx, Context: ctx,

View File

@@ -21,6 +21,7 @@ func (valueOnlyContext) Err() error {
return nil return nil
} }
// ValueOnlyFrom takes all values from the given ctx, without deadline and error control.
func ValueOnlyFrom(ctx context.Context) context.Context { func ValueOnlyFrom(ctx context.Context) context.Context {
return valueOnlyContext{ return valueOnlyContext{
Context: ctx, Context: ctx,

View File

@@ -14,6 +14,7 @@ const (
const timeToLive int64 = 10 const timeToLive int64 = 10
// TimeToLive is seconds to live in etcd.
var TimeToLive = timeToLive var TimeToLive = timeToLive
func extract(etcdKey string, index int) (string, bool) { func extract(etcdKey string, index int) (string, bool) {

View File

@@ -28,6 +28,9 @@ func TestExtract(t *testing.T) {
_, ok = extract("any", -1) _, ok = extract("any", -1)
assert.False(t, ok) assert.False(t, ok)
_, ok = extract("any", 10)
assert.False(t, ok)
} }
func TestMakeKey(t *testing.T) { func TestMakeKey(t *testing.T) {

View File

@@ -2,11 +2,13 @@ package discov
import "errors" import "errors"
// EtcdConf is the config item with the given key on etcd.
type EtcdConf struct { type EtcdConf struct {
Hosts []string Hosts []string
Key string Key string
} }
// Validate validates c.
func (c EtcdConf) Validate() error { func (c EtcdConf) Validate() error {
if len(c.Hosts) == 0 { if len(c.Hosts) == 0 {
return errors.New("empty etcd hosts") return errors.New("empty etcd hosts")

View File

@@ -1,47 +0,0 @@
package discov
import (
"github.com/tal-tech/go-zero/core/discov/internal"
"github.com/tal-tech/go-zero/core/logx"
)
type (
Facade struct {
endpoints []string
registry *internal.Registry
}
FacadeListener interface {
OnAdd(key, val string)
OnDelete(key string)
}
)
func NewFacade(endpoints []string) Facade {
return Facade{
endpoints: endpoints,
registry: internal.GetRegistry(),
}
}
func (f Facade) Client() internal.EtcdClient {
conn, err := f.registry.GetConn(f.endpoints)
logx.Must(err)
return conn
}
func (f Facade) Monitor(key string, l FacadeListener) {
f.registry.Monitor(f.endpoints, key, listenerAdapter{l})
}
type listenerAdapter struct {
l FacadeListener
}
func (la listenerAdapter) OnAdd(kv internal.KV) {
la.l.OnAdd(kv.Key, kv.Val)
}
func (la listenerAdapter) OnDelete(kv internal.KV) {
la.l.OnDelete(kv.Key)
}

View File

@@ -1,4 +1,5 @@
//go:generate mockgen -package internal -destination etcdclient_mock.go -source etcdclient.go EtcdClient //go:generate mockgen -package internal -destination etcdclient_mock.go -source etcdclient.go EtcdClient
package internal package internal
import ( import (
@@ -8,6 +9,7 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
) )
// EtcdClient interface represents an etcd client.
type EtcdClient interface { type EtcdClient interface {
ActiveConnection() *grpc.ClientConn ActiveConnection() *grpc.ClientConn
Close() error Close() error

View File

@@ -1,5 +1,6 @@
package internal package internal
// Listener interface wraps the OnUpdate method.
type Listener interface { type Listener interface {
OnUpdate(keys []string, values []string, newKey string) OnUpdate(keys []string, values []string, newKey string)
} }

View File

@@ -18,19 +18,31 @@ import (
) )
var ( var (
registryInstance = Registry{ registry = Registry{
clusters: make(map[string]*cluster), clusters: make(map[string]*cluster),
} }
connManager = syncx.NewResourceManager() connManager = syncx.NewResourceManager()
) )
// A Registry is a registry that manages the etcd client connections.
type Registry struct { type Registry struct {
clusters map[string]*cluster clusters map[string]*cluster
lock sync.Mutex lock sync.Mutex
} }
// GetRegistry returns a global Registry.
func GetRegistry() *Registry { func GetRegistry() *Registry {
return &registryInstance return &registry
}
// GetConn returns an etcd client connection associated with given endpoints.
func (r *Registry) GetConn(endpoints []string) (EtcdClient, error) {
return r.getCluster(endpoints).getClient()
}
// Monitor monitors the key on given etcd endpoints, notify with the given UpdateListener.
func (r *Registry) Monitor(endpoints []string, key string, l UpdateListener) error {
return r.getCluster(endpoints).monitor(key, l)
} }
func (r *Registry) getCluster(endpoints []string) *cluster { func (r *Registry) getCluster(endpoints []string) *cluster {
@@ -46,14 +58,6 @@ func (r *Registry) getCluster(endpoints []string) *cluster {
return c return c
} }
func (r *Registry) GetConn(endpoints []string) (EtcdClient, error) {
return r.getCluster(endpoints).getClient()
}
func (r *Registry) Monitor(endpoints []string, key string, l UpdateListener) error {
return r.getCluster(endpoints).monitor(key, l)
}
type cluster struct { type cluster struct {
endpoints []string endpoints []string
key string key string
@@ -288,6 +292,7 @@ func (c *cluster) watchConnState(cli EtcdClient) {
watcher.watch(cli.ActiveConnection()) watcher.watch(cli.ActiveConnection())
} }
// DialClient dials an etcd cluster with given endpoints.
func DialClient(endpoints []string) (EtcdClient, error) { func DialClient(endpoints []string) (EtcdClient, error) {
return clientv3.New(clientv3.Config{ return clientv3.New(clientv3.Config{
Endpoints: endpoints, Endpoints: endpoints,

View File

@@ -1,4 +1,5 @@
//go:generate mockgen -package internal -destination statewatcher_mock.go -source statewatcher.go etcdConn //go:generate mockgen -package internal -destination statewatcher_mock.go -source statewatcher.go etcdConn
package internal package internal
import ( import (
@@ -18,7 +19,8 @@ type (
disconnected bool disconnected bool
currentState connectivity.State currentState connectivity.State
listeners []func() listeners []func()
lock sync.Mutex // lock only guards listeners, because only listens can be accessed by other goroutines.
lock sync.Mutex
} }
) )
@@ -32,27 +34,33 @@ func (sw *stateWatcher) addListener(l func()) {
sw.lock.Unlock() sw.lock.Unlock()
} }
func (sw *stateWatcher) notifyListeners() {
sw.lock.Lock()
defer sw.lock.Unlock()
for _, l := range sw.listeners {
l()
}
}
func (sw *stateWatcher) updateState(conn etcdConn) {
sw.currentState = conn.GetState()
switch sw.currentState {
case connectivity.TransientFailure, connectivity.Shutdown:
sw.disconnected = true
case connectivity.Ready:
if sw.disconnected {
sw.disconnected = false
sw.notifyListeners()
}
}
}
func (sw *stateWatcher) watch(conn etcdConn) { func (sw *stateWatcher) watch(conn etcdConn) {
sw.currentState = conn.GetState() sw.currentState = conn.GetState()
for { for {
if conn.WaitForStateChange(context.Background(), sw.currentState) { if conn.WaitForStateChange(context.Background(), sw.currentState) {
newState := conn.GetState() sw.updateState(conn)
sw.lock.Lock()
sw.currentState = newState
switch newState {
case connectivity.TransientFailure, connectivity.Shutdown:
sw.disconnected = true
case connectivity.Ready:
if sw.disconnected {
sw.disconnected = false
for _, l := range sw.listeners {
l()
}
}
}
sw.lock.Unlock()
} }
} }
} }

View File

@@ -1,12 +1,15 @@
//go:generate mockgen -package internal -destination updatelistener_mock.go -source updatelistener.go UpdateListener //go:generate mockgen -package internal -destination updatelistener_mock.go -source updatelistener.go UpdateListener
package internal package internal
type ( type (
// A KV is used to store an etcd entry with key and value.
KV struct { KV struct {
Key string Key string
Val string Val string
} }
// UpdateListener wraps the OnAdd and OnDelete methods.
UpdateListener interface { UpdateListener interface {
OnAdd(kv KV) OnAdd(kv KV)
OnDelete(kv KV) OnDelete(kv KV)

View File

@@ -3,17 +3,22 @@ package internal
import "time" import "time"
const ( const (
// Delimiter is a separator that separates the etcd path.
Delimiter = '/'
autoSyncInterval = time.Minute autoSyncInterval = time.Minute
coolDownInterval = time.Second coolDownInterval = time.Second
dialTimeout = 5 * time.Second dialTimeout = 5 * time.Second
dialKeepAliveTime = 5 * time.Second dialKeepAliveTime = 5 * time.Second
requestTimeout = 3 * time.Second requestTimeout = 3 * time.Second
Delimiter = '/'
endpointsSeparator = "," endpointsSeparator = ","
) )
var ( var (
DialTimeout = dialTimeout // DialTimeout is the dial timeout.
DialTimeout = dialTimeout
// RequestTimeout is the request timeout.
RequestTimeout = requestTimeout RequestTimeout = requestTimeout
NewClient = DialClient // NewClient is used to create etcd clients.
NewClient = DialClient
) )

View File

@@ -0,0 +1,64 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: "etcd"
namespace: discov
labels:
app: "etcd"
spec:
serviceName: "etcd"
replicas: 5
template:
metadata:
name: "etcd"
labels:
app: "etcd"
spec:
volumes:
- name: etcd-pvc
persistentVolumeClaim:
claimName: etcd-pvc
containers:
- name: "etcd"
image: quay.io/coreos/etcd:latest
ports:
- containerPort: 2379
name: client
- containerPort: 2380
name: peer
env:
- name: CLUSTER_SIZE
value: "5"
- name: SET_NAME
value: "etcd"
- name: VOLNAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
volumeMounts:
- name: etcd-pvc
mountPath: /var/lib/etcd
subPathExpr: $(VOLNAME) # data mounted respectively in each pod
command:
- "/bin/sh"
- "-ecx"
- |
chmod 700 /var/lib/etcd
IP=$(hostname -i)
PEERS=""
for i in $(seq 0 $((${CLUSTER_SIZE} - 1))); do
PEERS="${PEERS}${PEERS:+,}${SET_NAME}-${i}=http://${SET_NAME}-${i}.${SET_NAME}:2380"
done
exec etcd --name ${HOSTNAME} \
--listen-peer-urls http://0.0.0.0:2380 \
--listen-client-urls http://0.0.0.0:2379 \
--advertise-client-urls http://${HOSTNAME}.${SET_NAME}.discov:2379 \
--initial-advertise-peer-urls http://${HOSTNAME}.${SET_NAME}:2380 \
--initial-cluster ${PEERS} \
--initial-cluster-state new \
--logger zap \
--data-dir /var/lib/etcd \
--auto-compaction-retention 1

View File

@@ -11,8 +11,10 @@ import (
) )
type ( type (
// PublisherOption defines the method to customize a Publisher.
PublisherOption func(client *Publisher) PublisherOption func(client *Publisher)
// A Publisher can be used to publish the value to an etcd cluster on the given key.
Publisher struct { Publisher struct {
endpoints []string endpoints []string
key string key string
@@ -26,6 +28,10 @@ type (
} }
) )
// NewPublisher returns a Publisher.
// endpoints is the hosts of the etcd cluster.
// key:value are a pair to be published.
// opts are used to customize the Publisher.
func NewPublisher(endpoints []string, key, value string, opts ...PublisherOption) *Publisher { func NewPublisher(endpoints []string, key, value string, opts ...PublisherOption) *Publisher {
publisher := &Publisher{ publisher := &Publisher{
endpoints: endpoints, endpoints: endpoints,
@@ -43,6 +49,7 @@ func NewPublisher(endpoints []string, key, value string, opts ...PublisherOption
return publisher return publisher
} }
// KeepAlive keeps key:value alive.
func (p *Publisher) KeepAlive() error { func (p *Publisher) KeepAlive() error {
cli, err := internal.GetRegistry().GetConn(p.endpoints) cli, err := internal.GetRegistry().GetConn(p.endpoints)
if err != nil { if err != nil {
@@ -61,14 +68,17 @@ func (p *Publisher) KeepAlive() error {
return p.keepAliveAsync(cli) return p.keepAliveAsync(cli)
} }
// Pause pauses the renewing of key:value.
func (p *Publisher) Pause() { func (p *Publisher) Pause() {
p.pauseChan <- lang.Placeholder p.pauseChan <- lang.Placeholder
} }
// Resume resumes the renewing of key:value.
func (p *Publisher) Resume() { func (p *Publisher) Resume() {
p.resumeChan <- lang.Placeholder p.resumeChan <- lang.Placeholder
} }
// Stop stops the renewing and revokes the registration.
func (p *Publisher) Stop() { func (p *Publisher) Stop() {
p.quit.Close() p.quit.Close()
} }
@@ -135,6 +145,7 @@ func (p *Publisher) revoke(cli internal.EtcdClient) {
} }
} }
// WithId customizes a Publisher with the id.
func WithId(id int64) PublisherOption { func WithId(id int64) PublisherOption {
return func(publisher *Publisher) { return func(publisher *Publisher) {
publisher.id = id publisher.id = id

View File

@@ -4,10 +4,12 @@ import (
"errors" "errors"
"sync" "sync"
"testing" "testing"
"time"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/discov/internal" "github.com/tal-tech/go-zero/core/discov/internal"
"github.com/tal-tech/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"go.etcd.io/etcd/clientv3" "go.etcd.io/etcd/clientv3"
) )
@@ -152,3 +154,16 @@ func TestPublisher_keepAliveAsyncPause(t *testing.T) {
pub.Pause() pub.Pause()
wg.Wait() wg.Wait()
} }
func TestPublisher_Resume(t *testing.T) {
publisher := new(Publisher)
publisher.resumeChan = make(chan lang.PlaceholderType)
go func() {
publisher.Resume()
}()
go func() {
time.Sleep(time.Minute)
t.Fail()
}()
<-publisher.resumeChan
}

View File

@@ -13,13 +13,19 @@ type (
exclusive bool exclusive bool
} }
// SubOption defines the method to customize a Subscriber.
SubOption func(opts *subOptions) SubOption func(opts *subOptions)
// A Subscriber is used to subscribe the given key on a etcd cluster.
Subscriber struct { Subscriber struct {
items *container items *container
} }
) )
// NewSubscriber returns a Subscriber.
// endpoints is the hosts of the etcd cluster.
// key is the key to subscribe.
// opts are used to customize the Subscriber.
func NewSubscriber(endpoints []string, key string, opts ...SubOption) (*Subscriber, error) { func NewSubscriber(endpoints []string, key string, opts ...SubOption) (*Subscriber, error) {
var subOpts subOptions var subOpts subOptions
for _, opt := range opts { for _, opt := range opts {
@@ -36,15 +42,17 @@ func NewSubscriber(endpoints []string, key string, opts ...SubOption) (*Subscrib
return sub, nil return sub, nil
} }
// AddListener adds listener to s.
func (s *Subscriber) AddListener(listener func()) { func (s *Subscriber) AddListener(listener func()) {
s.items.addListener(listener) s.items.addListener(listener)
} }
// Values returns all the subscription values.
func (s *Subscriber) Values() []string { func (s *Subscriber) Values() []string {
return s.items.getValues() return s.items.getValues()
} }
// exclusive means that key value can only be 1:1, // Exclusive means that key value can only be 1:1,
// which means later added value will remove the keys associated with the same value previously. // which means later added value will remove the keys associated with the same value previously.
func Exclusive() SubOption { func Exclusive() SubOption {
return func(opts *subOptions) { return func(opts *subOptions) {
@@ -100,9 +108,9 @@ func (c *container) addKv(key, value string) ([]string, bool) {
if early { if early {
return previous, true return previous, true
} else {
return nil, false
} }
return nil, false
} }
func (c *container) addListener(listener func()) { func (c *container) addListener(listener func()) {

View File

@@ -1,6 +1,7 @@
package discov package discov
import ( import (
"sync/atomic"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -198,3 +199,18 @@ func TestContainer(t *testing.T) {
} }
} }
} }
func TestSubscriber(t *testing.T) {
var opt subOptions
Exclusive()(&opt)
sub := new(Subscriber)
sub.items = newContainer(opt.exclusive)
var count int32
sub.AddListener(func() {
atomic.AddInt32(&count, 1)
})
sub.items.notifyChange()
assert.Empty(t, sub.Values())
assert.Equal(t, int32(1), atomic.LoadInt32(&count))
}

View File

@@ -2,14 +2,17 @@ package errorx
import "sync/atomic" import "sync/atomic"
// AtomicError defines an atomic error.
type AtomicError struct { type AtomicError struct {
err atomic.Value // error err atomic.Value // error
} }
// Set sets the error.
func (ae *AtomicError) Set(err error) { func (ae *AtomicError) Set(err error) {
ae.err.Store(err) ae.err.Store(err)
} }
// Load returns the error.
func (ae *AtomicError) Load() error { func (ae *AtomicError) Load() error {
if v := ae.err.Load(); v != nil { if v := ae.err.Load(); v != nil {
return v.(error) return v.(error)

View File

@@ -3,6 +3,7 @@ package errorx
import "bytes" import "bytes"
type ( type (
// A BatchError is an error that can hold multiple errors.
BatchError struct { BatchError struct {
errs errorArray errs errorArray
} }
@@ -10,12 +11,14 @@ type (
errorArray []error errorArray []error
) )
// Add adds err to be.
func (be *BatchError) Add(err error) { func (be *BatchError) Add(err error) {
if err != nil { if err != nil {
be.errs = append(be.errs, err) be.errs = append(be.errs, err)
} }
} }
// Err returns an error that represents all errors.
func (be *BatchError) Err() error { func (be *BatchError) Err() error {
switch len(be.errs) { switch len(be.errs) {
case 0: case 0:
@@ -27,10 +30,12 @@ func (be *BatchError) Err() error {
} }
} }
// NotNil checks if any error inside.
func (be *BatchError) NotNil() bool { func (be *BatchError) NotNil() bool {
return len(be.errs) > 0 return len(be.errs) > 0
} }
// Error returns a string that represents inside errors.
func (ea errorArray) Error() string { func (ea errorArray) Error() string {
var buf bytes.Buffer var buf bytes.Buffer

View File

@@ -1,5 +1,6 @@
package errorx package errorx
// Chain runs funs one by one until an error occurred.
func Chain(fns ...func() error) error { func Chain(fns ...func() error) error {
for _, fn := range fns { for _, fn := range fns {
if err := fn(); err != nil { if err := fn(); err != nil {

View File

@@ -8,7 +8,7 @@ import (
) )
func TestChain(t *testing.T) { func TestChain(t *testing.T) {
var errDummy = errors.New("dummy") errDummy := errors.New("dummy")
assert.Nil(t, Chain(func() error { assert.Nil(t, Chain(func() error {
return nil return nil
}, func() error { }, func() error {

View File

@@ -5,8 +5,12 @@ import "time"
const defaultBulkTasks = 1000 const defaultBulkTasks = 1000
type ( type (
// BulkOption defines the method to customize a BulkExecutor.
BulkOption func(options *bulkOptions) BulkOption func(options *bulkOptions)
// A BulkExecutor is an executor that can execute tasks on either requirement meets:
// 1. up to given size of tasks
// 2. flush interval time elapsed
BulkExecutor struct { BulkExecutor struct {
executor *PeriodicalExecutor executor *PeriodicalExecutor
container *bulkContainer container *bulkContainer
@@ -18,6 +22,7 @@ type (
} }
) )
// NewBulkExecutor returns a BulkExecutor.
func NewBulkExecutor(execute Execute, opts ...BulkOption) *BulkExecutor { func NewBulkExecutor(execute Execute, opts ...BulkOption) *BulkExecutor {
options := newBulkOptions() options := newBulkOptions()
for _, opt := range opts { for _, opt := range opts {
@@ -36,25 +41,30 @@ func NewBulkExecutor(execute Execute, opts ...BulkOption) *BulkExecutor {
return executor return executor
} }
// Add adds task into be.
func (be *BulkExecutor) Add(task interface{}) error { func (be *BulkExecutor) Add(task interface{}) error {
be.executor.Add(task) be.executor.Add(task)
return nil return nil
} }
// Flush forces be to flush and execute tasks.
func (be *BulkExecutor) Flush() { func (be *BulkExecutor) Flush() {
be.executor.Flush() be.executor.Flush()
} }
// Wait waits be to done with the task execution.
func (be *BulkExecutor) Wait() { func (be *BulkExecutor) Wait() {
be.executor.Wait() be.executor.Wait()
} }
// WithBulkTasks customizes a BulkExecutor with given tasks limit.
func WithBulkTasks(tasks int) BulkOption { func WithBulkTasks(tasks int) BulkOption {
return func(options *bulkOptions) { return func(options *bulkOptions) {
options.cachedTasks = tasks options.cachedTasks = tasks
} }
} }
// WithBulkInterval customizes a BulkExecutor with given flush interval.
func WithBulkInterval(duration time.Duration) BulkOption { func WithBulkInterval(duration time.Duration) BulkOption {
return func(options *bulkOptions) { return func(options *bulkOptions) {
options.flushInterval = duration options.flushInterval = duration

View File

@@ -5,8 +5,12 @@ import "time"
const defaultChunkSize = 1024 * 1024 // 1M const defaultChunkSize = 1024 * 1024 // 1M
type ( type (
// ChunkOption defines the method to customize a ChunkExecutor.
ChunkOption func(options *chunkOptions) ChunkOption func(options *chunkOptions)
// A ChunkExecutor is an executor to execute tasks when either requirement meets:
// 1. up to given chunk size
// 2. flush interval elapsed
ChunkExecutor struct { ChunkExecutor struct {
executor *PeriodicalExecutor executor *PeriodicalExecutor
container *chunkContainer container *chunkContainer
@@ -18,6 +22,7 @@ type (
} }
) )
// NewChunkExecutor returns a ChunkExecutor.
func NewChunkExecutor(execute Execute, opts ...ChunkOption) *ChunkExecutor { func NewChunkExecutor(execute Execute, opts ...ChunkOption) *ChunkExecutor {
options := newChunkOptions() options := newChunkOptions()
for _, opt := range opts { for _, opt := range opts {
@@ -36,6 +41,7 @@ func NewChunkExecutor(execute Execute, opts ...ChunkOption) *ChunkExecutor {
return executor return executor
} }
// Add adds task with given chunk size into ce.
func (ce *ChunkExecutor) Add(task interface{}, size int) error { func (ce *ChunkExecutor) Add(task interface{}, size int) error {
ce.executor.Add(chunk{ ce.executor.Add(chunk{
val: task, val: task,
@@ -44,20 +50,24 @@ func (ce *ChunkExecutor) Add(task interface{}, size int) error {
return nil return nil
} }
// Flush forces ce to flush and execute tasks.
func (ce *ChunkExecutor) Flush() { func (ce *ChunkExecutor) Flush() {
ce.executor.Flush() ce.executor.Flush()
} }
// Wait waits the execution to be done.
func (ce *ChunkExecutor) Wait() { func (ce *ChunkExecutor) Wait() {
ce.executor.Wait() ce.executor.Wait()
} }
// WithChunkBytes customizes a ChunkExecutor with the given chunk size.
func WithChunkBytes(size int) ChunkOption { func WithChunkBytes(size int) ChunkOption {
return func(options *chunkOptions) { return func(options *chunkOptions) {
options.chunkSize = size options.chunkSize = size
} }
} }
// WithFlushInterval customizes a ChunkExecutor with the given flush interval.
func WithFlushInterval(duration time.Duration) ChunkOption { func WithFlushInterval(duration time.Duration) ChunkOption {
return func(options *chunkOptions) { return func(options *chunkOptions) {
options.flushInterval = duration options.flushInterval = duration

View File

@@ -7,6 +7,7 @@ import (
"github.com/tal-tech/go-zero/core/threading" "github.com/tal-tech/go-zero/core/threading"
) )
// A DelayExecutor delays a tasks on given delay interval.
type DelayExecutor struct { type DelayExecutor struct {
fn func() fn func()
delay time.Duration delay time.Duration
@@ -14,6 +15,7 @@ type DelayExecutor struct {
lock sync.Mutex lock sync.Mutex
} }
// NewDelayExecutor returns a DelayExecutor with given fn and delay.
func NewDelayExecutor(fn func(), delay time.Duration) *DelayExecutor { func NewDelayExecutor(fn func(), delay time.Duration) *DelayExecutor {
return &DelayExecutor{ return &DelayExecutor{
fn: fn, fn: fn,
@@ -21,6 +23,7 @@ func NewDelayExecutor(fn func(), delay time.Duration) *DelayExecutor {
} }
} }
// Trigger triggers the task to be executed after given delay, safe to trigger more than once.
func (de *DelayExecutor) Trigger() { func (de *DelayExecutor) Trigger() {
de.lock.Lock() de.lock.Lock()
defer de.lock.Unlock() defer de.lock.Unlock()

View File

@@ -7,11 +7,13 @@ import (
"github.com/tal-tech/go-zero/core/timex" "github.com/tal-tech/go-zero/core/timex"
) )
// A LessExecutor is an executor to limit execution once within given time interval.
type LessExecutor struct { type LessExecutor struct {
threshold time.Duration threshold time.Duration
lastTime *syncx.AtomicDuration lastTime *syncx.AtomicDuration
} }
// NewLessExecutor returns a LessExecutor with given threshold as time interval.
func NewLessExecutor(threshold time.Duration) *LessExecutor { func NewLessExecutor(threshold time.Duration) *LessExecutor {
return &LessExecutor{ return &LessExecutor{
threshold: threshold, threshold: threshold,
@@ -19,6 +21,8 @@ func NewLessExecutor(threshold time.Duration) *LessExecutor {
} }
} }
// DoOrDiscard executes or discards the task depends on if
// another task was executed within the time interval.
func (le *LessExecutor) DoOrDiscard(execute func()) bool { func (le *LessExecutor) DoOrDiscard(execute func()) bool {
now := timex.Now() now := timex.Now()
lastTime := le.lastTime.Load() lastTime := le.lastTime.Load()

View File

@@ -3,6 +3,7 @@ package executors
import ( import (
"reflect" "reflect"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/lang"
@@ -15,7 +16,7 @@ import (
const idleRound = 10 const idleRound = 10
type ( type (
// A type that satisfies executors.TaskContainer can be used as the underlying // TaskContainer interface defines a type that can be used as the underlying
// container that used to do periodical executions. // container that used to do periodical executions.
TaskContainer interface { TaskContainer interface {
// AddTask adds the task into the container. // AddTask adds the task into the container.
@@ -27,6 +28,7 @@ type (
RemoveAll() interface{} RemoveAll() interface{}
} }
// A PeriodicalExecutor is an executor that periodically execute tasks.
PeriodicalExecutor struct { PeriodicalExecutor struct {
commander chan interface{} commander chan interface{}
interval time.Duration interval time.Duration
@@ -35,12 +37,14 @@ type (
// avoid race condition on waitGroup when calling wg.Add/Done/Wait(...) // avoid race condition on waitGroup when calling wg.Add/Done/Wait(...)
wgBarrier syncx.Barrier wgBarrier syncx.Barrier
confirmChan chan lang.PlaceholderType confirmChan chan lang.PlaceholderType
inflight int32
guarded bool guarded bool
newTicker func(duration time.Duration) timex.Ticker newTicker func(duration time.Duration) timex.Ticker
lock sync.Mutex lock sync.Mutex
} }
) )
// NewPeriodicalExecutor returns a PeriodicalExecutor with given interval and container.
func NewPeriodicalExecutor(interval time.Duration, container TaskContainer) *PeriodicalExecutor { func NewPeriodicalExecutor(interval time.Duration, container TaskContainer) *PeriodicalExecutor {
executor := &PeriodicalExecutor{ executor := &PeriodicalExecutor{
// buffer 1 to let the caller go quickly // buffer 1 to let the caller go quickly
@@ -49,7 +53,7 @@ func NewPeriodicalExecutor(interval time.Duration, container TaskContainer) *Per
container: container, container: container,
confirmChan: make(chan lang.PlaceholderType), confirmChan: make(chan lang.PlaceholderType),
newTicker: func(d time.Duration) timex.Ticker { newTicker: func(d time.Duration) timex.Ticker {
return timex.NewTicker(interval) return timex.NewTicker(d)
}, },
} }
proc.AddShutdownListener(func() { proc.AddShutdownListener(func() {
@@ -59,6 +63,7 @@ func NewPeriodicalExecutor(interval time.Duration, container TaskContainer) *Per
return executor return executor
} }
// Add adds tasks into pe.
func (pe *PeriodicalExecutor) Add(task interface{}) { func (pe *PeriodicalExecutor) Add(task interface{}) {
if vals, ok := pe.addAndCheck(task); ok { if vals, ok := pe.addAndCheck(task); ok {
pe.commander <- vals pe.commander <- vals
@@ -66,6 +71,7 @@ func (pe *PeriodicalExecutor) Add(task interface{}) {
} }
} }
// Flush forces pe to execute tasks.
func (pe *PeriodicalExecutor) Flush() bool { func (pe *PeriodicalExecutor) Flush() bool {
pe.enterExecution() pe.enterExecution()
return pe.executeTasks(func() interface{} { return pe.executeTasks(func() interface{} {
@@ -75,12 +81,14 @@ func (pe *PeriodicalExecutor) Flush() bool {
}()) }())
} }
// Sync lets caller to run fn thread-safe with pe, especially for the underlying container.
func (pe *PeriodicalExecutor) Sync(fn func()) { func (pe *PeriodicalExecutor) Sync(fn func()) {
pe.lock.Lock() pe.lock.Lock()
defer pe.lock.Unlock() defer pe.lock.Unlock()
fn() fn()
} }
// Wait waits the execution to be done.
func (pe *PeriodicalExecutor) Wait() { func (pe *PeriodicalExecutor) Wait() {
pe.Flush() pe.Flush()
pe.wgBarrier.Guard(func() { pe.wgBarrier.Guard(func() {
@@ -91,18 +99,16 @@ func (pe *PeriodicalExecutor) Wait() {
func (pe *PeriodicalExecutor) addAndCheck(task interface{}) (interface{}, bool) { func (pe *PeriodicalExecutor) addAndCheck(task interface{}) (interface{}, bool) {
pe.lock.Lock() pe.lock.Lock()
defer func() { defer func() {
var start bool
if !pe.guarded { if !pe.guarded {
pe.guarded = true pe.guarded = true
start = true // defer to unlock quickly
defer pe.backgroundFlush()
} }
pe.lock.Unlock() pe.lock.Unlock()
if start {
pe.backgroundFlush()
}
}() }()
if pe.container.AddTask(task) { if pe.container.AddTask(task) {
atomic.AddInt32(&pe.inflight, 1)
return pe.container.RemoveAll(), true return pe.container.RemoveAll(), true
} }
@@ -111,6 +117,9 @@ func (pe *PeriodicalExecutor) addAndCheck(task interface{}) (interface{}, bool)
func (pe *PeriodicalExecutor) backgroundFlush() { func (pe *PeriodicalExecutor) backgroundFlush() {
threading.GoSafe(func() { threading.GoSafe(func() {
// flush before quit goroutine to avoid missing tasks
defer pe.Flush()
ticker := pe.newTicker(pe.interval) ticker := pe.newTicker(pe.interval)
defer ticker.Stop() defer ticker.Stop()
@@ -120,6 +129,7 @@ func (pe *PeriodicalExecutor) backgroundFlush() {
select { select {
case vals := <-pe.commander: case vals := <-pe.commander:
commanded = true commanded = true
atomic.AddInt32(&pe.inflight, -1)
pe.enterExecution() pe.enterExecution()
pe.confirmChan <- lang.Placeholder pe.confirmChan <- lang.Placeholder
pe.executeTasks(vals) pe.executeTasks(vals)
@@ -129,13 +139,7 @@ func (pe *PeriodicalExecutor) backgroundFlush() {
commanded = false commanded = false
} else if pe.Flush() { } else if pe.Flush() {
last = timex.Now() last = timex.Now()
} else if timex.Since(last) > pe.interval*idleRound { } else if pe.shallQuit(last) {
pe.lock.Lock()
pe.guarded = false
pe.lock.Unlock()
// flush again to avoid missing tasks
pe.Flush()
return return
} }
} }
@@ -178,3 +182,19 @@ func (pe *PeriodicalExecutor) hasTasks(tasks interface{}) bool {
return true return true
} }
} }
func (pe *PeriodicalExecutor) shallQuit(last time.Duration) (stop bool) {
if timex.Since(last) <= pe.interval*idleRound {
return
}
// checking pe.inflight and setting pe.guarded should be locked together
pe.lock.Lock()
if atomic.LoadInt32(&pe.inflight) == 0 {
pe.guarded = false
stop = true
}
pe.lock.Unlock()
return
}

View File

@@ -140,6 +140,26 @@ func TestPeriodicalExecutor_WaitFast(t *testing.T) {
assert.Equal(t, total, cnt) assert.Equal(t, total, cnt)
} }
func TestPeriodicalExecutor_Deadlock(t *testing.T) {
executor := NewBulkExecutor(func(tasks []interface{}) {
}, WithBulkTasks(1), WithBulkInterval(time.Millisecond))
for i := 0; i < 1e5; i++ {
executor.Add(1)
}
}
func TestPeriodicalExecutor_hasTasks(t *testing.T) {
ticker := timex.NewFakeTicker()
defer ticker.Stop()
exec := NewPeriodicalExecutor(time.Millisecond, newContainer(time.Millisecond, nil))
exec.newTicker = func(d time.Duration) timex.Ticker {
return ticker
}
assert.False(t, exec.hasTasks(nil))
assert.True(t, exec.hasTasks(1))
}
// go test -benchtime 10s -bench . // go test -benchtime 10s -bench .
func BenchmarkExecutor(b *testing.B) { func BenchmarkExecutor(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()

View File

@@ -4,4 +4,5 @@ import "time"
const defaultFlushInterval = time.Second const defaultFlushInterval = time.Second
// Execute defines the method to execute tasks.
type Execute func(tasks []interface{}) type Execute func(tasks []interface{})

View File

@@ -7,6 +7,7 @@ import (
const bufSize = 1024 const bufSize = 1024
// FirstLine returns the first line of the file.
func FirstLine(filename string) (string, error) { func FirstLine(filename string) (string, error) {
file, err := os.Open(filename) file, err := os.Open(filename)
if err != nil { if err != nil {
@@ -17,6 +18,7 @@ func FirstLine(filename string) (string, error) {
return firstLine(file) return firstLine(file)
} }
// LastLine returns the last line of the file.
func LastLine(filename string) (string, error) { func LastLine(filename string) (string, error) {
file, err := os.Open(filename) file, err := os.Open(filename)
if err != nil { if err != nil {
@@ -69,11 +71,11 @@ func lastLine(filename string, file *os.File) (string, error) {
if buf[n-1] == '\n' { if buf[n-1] == '\n' {
buf = buf[:n-1] buf = buf[:n-1]
n -= 1 n--
} else { } else {
buf = buf[:n] buf = buf[:n]
} }
for n -= 1; n >= 0; n-- { for n--; n >= 0; n-- {
if buf[n] == '\n' { if buf[n] == '\n' {
return string(append(buf[n+1:], last...)), nil return string(append(buf[n+1:], last...)), nil
} }

View File

@@ -5,12 +5,15 @@ import (
"os" "os"
) )
// OffsetRange represents a content block of a file.
type OffsetRange struct { type OffsetRange struct {
File string File string
Start int64 Start int64
Stop int64 Stop int64
} }
// SplitLineChunks splits file into chunks.
// The whole line are guaranteed to be split in the same chunk.
func SplitLineChunks(filename string, chunks int) ([]OffsetRange, error) { func SplitLineChunks(filename string, chunks int) ([]OffsetRange, error) {
info, err := os.Stat(filename) info, err := os.Stat(filename)
if err != nil { if err != nil {

View File

@@ -3,8 +3,11 @@ package filex
import "gopkg.in/cheggaaa/pb.v1" import "gopkg.in/cheggaaa/pb.v1"
type ( type (
// A Scanner is used to read lines.
Scanner interface { Scanner interface {
// Scan checks if has remaining to read.
Scan() bool Scan() bool
// Text returns next line.
Text() string Text() string
} }
@@ -14,6 +17,7 @@ type (
} }
) )
// NewProgressScanner returns a Scanner with progress indicator.
func NewProgressScanner(scanner Scanner, bar *pb.ProgressBar) Scanner { func NewProgressScanner(scanner Scanner, bar *pb.ProgressBar) Scanner {
return &progressScanner{ return &progressScanner{
Scanner: scanner, Scanner: scanner,

View File

@@ -5,12 +5,14 @@ import (
"os" "os"
) )
// A RangeReader is used to read a range of content from a file.
type RangeReader struct { type RangeReader struct {
file *os.File file *os.File
start int64 start int64
stop int64 stop int64
} }
// NewRangeReader returns a RangeReader, which will read the range of content from file.
func NewRangeReader(file *os.File, start, stop int64) *RangeReader { func NewRangeReader(file *os.File, start, stop int64) *RangeReader {
return &RangeReader{ return &RangeReader{
file: file, file: file,
@@ -19,6 +21,7 @@ func NewRangeReader(file *os.File, start, stop int64) *RangeReader {
} }
} }
// Read reads the range of content into p.
func (rr *RangeReader) Read(p []byte) (n int, err error) { func (rr *RangeReader) Read(p []byte) (n int, err error) {
stat, err := rr.file.Stat() stat, err := rr.file.Stat()
if err != nil { if err != nil {

View File

@@ -7,6 +7,7 @@ import (
"syscall" "syscall"
) )
// CloseOnExec makes sure closing the file on process forking.
func CloseOnExec(file *os.File) { func CloseOnExec(file *os.File) {
if file != nil { if file != nil {
syscall.CloseOnExec(int(file.Fd())) syscall.CloseOnExec(int(file.Fd()))

View File

@@ -2,6 +2,7 @@ package fx
import "github.com/tal-tech/go-zero/core/threading" import "github.com/tal-tech/go-zero/core/threading"
// Parallel runs fns parallelly and waits for done.
func Parallel(fns ...func()) { func Parallel(fns ...func()) {
group := threading.NewRoutineGroup() group := threading.NewRoutineGroup()
for _, fn := range fns { for _, fn := range fns {

View File

@@ -5,6 +5,7 @@ import "github.com/tal-tech/go-zero/core/errorx"
const defaultRetryTimes = 3 const defaultRetryTimes = 3
type ( type (
// RetryOption defines the method to customize DoWithRetry.
RetryOption func(*retryOptions) RetryOption func(*retryOptions)
retryOptions struct { retryOptions struct {
@@ -12,8 +13,9 @@ type (
} }
) )
func DoWithRetries(fn func() error, opts ...RetryOption) error { // DoWithRetry runs fn, and retries if failed. Default to retry 3 times.
var options = newRetryOptions() func DoWithRetry(fn func() error, opts ...RetryOption) error {
options := newRetryOptions()
for _, opt := range opts { for _, opt := range opts {
opt(options) opt(options)
} }
@@ -30,7 +32,8 @@ func DoWithRetries(fn func() error, opts ...RetryOption) error {
return berr.Err() return berr.Err()
} }
func WithRetries(times int) RetryOption { // WithRetry customize a DoWithRetry call with given retry times.
func WithRetry(times int) RetryOption {
return func(options *retryOptions) { return func(options *retryOptions) {
options.times = times options.times = times
} }

View File

@@ -8,12 +8,12 @@ import (
) )
func TestRetry(t *testing.T) { func TestRetry(t *testing.T) {
assert.NotNil(t, DoWithRetries(func() error { assert.NotNil(t, DoWithRetry(func() error {
return errors.New("any") return errors.New("any")
})) }))
var times int var times int
assert.Nil(t, DoWithRetries(func() error { assert.Nil(t, DoWithRetry(func() error {
times++ times++
if times == defaultRetryTimes { if times == defaultRetryTimes {
return nil return nil
@@ -22,7 +22,7 @@ func TestRetry(t *testing.T) {
})) }))
times = 0 times = 0
assert.NotNil(t, DoWithRetries(func() error { assert.NotNil(t, DoWithRetry(func() error {
times++ times++
if times == defaultRetryTimes+1 { if times == defaultRetryTimes+1 {
return nil return nil
@@ -30,13 +30,13 @@ func TestRetry(t *testing.T) {
return errors.New("any") return errors.New("any")
})) }))
var total = 2 * defaultRetryTimes total := 2 * defaultRetryTimes
times = 0 times = 0
assert.Nil(t, DoWithRetries(func() error { assert.Nil(t, DoWithRetry(func() error {
times++ times++
if times == total { if times == total {
return nil return nil
} }
return errors.New("any") return errors.New("any")
}, WithRetries(total))) }, WithRetry(total)))
} }

View File

@@ -20,18 +20,30 @@ type (
workers int workers int
} }
FilterFunc func(item interface{}) bool // FilterFunc defines the method to filter a Stream.
ForAllFunc func(pipe <-chan interface{}) FilterFunc func(item interface{}) bool
ForEachFunc func(item interface{}) // ForAllFunc defines the method to handle all elements in a Stream.
ForAllFunc func(pipe <-chan interface{})
// ForEachFunc defines the method to handle each element in a Stream.
ForEachFunc func(item interface{})
// GenerateFunc defines the method to send elements into a Stream.
GenerateFunc func(source chan<- interface{}) GenerateFunc func(source chan<- interface{})
KeyFunc func(item interface{}) interface{} // KeyFunc defines the method to generate keys for the elements in a Stream.
LessFunc func(a, b interface{}) bool KeyFunc func(item interface{}) interface{}
MapFunc func(item interface{}) interface{} // LessFunc defines the method to compare the elements in a Stream.
Option func(opts *rxOptions) LessFunc func(a, b interface{}) bool
// MapFunc defines the method to map each element to another object in a Stream.
MapFunc func(item interface{}) interface{}
// Option defines the method to customize a Stream.
Option func(opts *rxOptions)
// ParallelFunc defines the method to handle elements parallelly.
ParallelFunc func(item interface{}) ParallelFunc func(item interface{})
ReduceFunc func(pipe <-chan interface{}) (interface{}, error) // ReduceFunc defines the method to reduce all the elements in a Stream.
WalkFunc func(item interface{}, pipe chan<- interface{}) ReduceFunc func(pipe <-chan interface{}) (interface{}, error)
// WalkFunc defines the method to walk through all the elements in a Stream.
WalkFunc func(item interface{}, pipe chan<- interface{})
// A Stream is a stream that can be used to do stream processing.
Stream struct { Stream struct {
source <-chan interface{} source <-chan interface{}
} }
@@ -159,6 +171,7 @@ func (p Stream) Group(fn KeyFunc) Stream {
return Range(source) return Range(source)
} }
// Head returns the first n elements in p.
func (p Stream) Head(n int64) Stream { func (p Stream) Head(n int64) Stream {
if n < 1 { if n < 1 {
panic("n must be greater than 0") panic("n must be greater than 0")
@@ -187,7 +200,7 @@ func (p Stream) Head(n int64) Stream {
return Range(source) return Range(source)
} }
// Maps converts each item to another corresponding item, which means it's a 1:1 model. // Map converts each item to another corresponding item, which means it's a 1:1 model.
func (p Stream) Map(fn MapFunc, opts ...Option) Stream { func (p Stream) Map(fn MapFunc, opts ...Option) Stream {
return p.Walk(func(item interface{}, pipe chan<- interface{}) { return p.Walk(func(item interface{}, pipe chan<- interface{}) {
pipe <- fn(item) pipe <- fn(item)
@@ -274,6 +287,7 @@ func (p Stream) Split(n int) Stream {
return Range(source) return Range(source)
} }
// Tail returns the last n elements in p.
func (p Stream) Tail(n int64) Stream { func (p Stream) Tail(n int64) Stream {
if n < 1 { if n < 1 {
panic("n should be greater than 0") panic("n should be greater than 0")
@@ -300,9 +314,9 @@ func (p Stream) Walk(fn WalkFunc, opts ...Option) Stream {
option := buildOptions(opts...) option := buildOptions(opts...)
if option.unlimitedWorkers { if option.unlimitedWorkers {
return p.walkUnlimited(fn, option) return p.walkUnlimited(fn, option)
} else {
return p.walkLimited(fn, option)
} }
return p.walkLimited(fn, option)
} }
func (p Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream { func (p Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {

View File

@@ -6,13 +6,17 @@ import (
) )
var ( var (
// ErrCanceled is the error returned when the context is canceled.
ErrCanceled = context.Canceled ErrCanceled = context.Canceled
ErrTimeout = context.DeadlineExceeded // ErrTimeout is the error returned when the context's deadline passes.
ErrTimeout = context.DeadlineExceeded
) )
type FxOption func() context.Context // DoOption defines the method to customize a DoWithTimeout call.
type DoOption func() context.Context
func DoWithTimeout(fn func() error, timeout time.Duration, opts ...FxOption) error { // DoWithTimeout runs fn with timeout control.
func DoWithTimeout(fn func() error, timeout time.Duration, opts ...DoOption) error {
parentCtx := context.Background() parentCtx := context.Background()
for _, opt := range opts { for _, opt := range opts {
parentCtx = opt() parentCtx = opt()
@@ -20,7 +24,8 @@ func DoWithTimeout(fn func() error, timeout time.Duration, opts ...FxOption) err
ctx, cancel := context.WithTimeout(parentCtx, timeout) ctx, cancel := context.WithTimeout(parentCtx, timeout)
defer cancel() defer cancel()
done := make(chan error) // create channel with buffer size 1 to avoid goroutine leak
done := make(chan error, 1)
panicChan := make(chan interface{}, 1) panicChan := make(chan interface{}, 1)
go func() { go func() {
defer func() { defer func() {
@@ -29,7 +34,6 @@ func DoWithTimeout(fn func() error, timeout time.Duration, opts ...FxOption) err
} }
}() }()
done <- fn() done <- fn()
close(done)
}() }()
select { select {
@@ -42,7 +46,8 @@ func DoWithTimeout(fn func() error, timeout time.Duration, opts ...FxOption) err
} }
} }
func WithContext(ctx context.Context) FxOption { // WithContext customizes a DoWithTimeout call with given ctx.
func WithContext(ctx context.Context) DoOption {
return func() context.Context { return func() context.Context {
return ctx return ctx
} }

View File

@@ -11,6 +11,7 @@ import (
) )
const ( const (
// TopWeight is the top weight that one entry might set.
TopWeight = 100 TopWeight = 100
minReplicas = 100 minReplicas = 100
@@ -18,10 +19,12 @@ const (
) )
type ( type (
HashFunc func(data []byte) uint64 // Func defines the hash method.
Func func(data []byte) uint64
// A ConsistentHash is a ring hash implementation.
ConsistentHash struct { ConsistentHash struct {
hashFunc HashFunc hashFunc Func
replicas int replicas int
keys []uint64 keys []uint64
ring map[uint64][]interface{} ring map[uint64][]interface{}
@@ -30,11 +33,13 @@ type (
} }
) )
// NewConsistentHash returns a ConsistentHash.
func NewConsistentHash() *ConsistentHash { func NewConsistentHash() *ConsistentHash {
return NewCustomConsistentHash(minReplicas, Hash) return NewCustomConsistentHash(minReplicas, Hash)
} }
func NewCustomConsistentHash(replicas int, fn HashFunc) *ConsistentHash { // NewCustomConsistentHash returns a ConsistentHash with given replicas and hash func.
func NewCustomConsistentHash(replicas int, fn Func) *ConsistentHash {
if replicas < minReplicas { if replicas < minReplicas {
replicas = minReplicas replicas = minReplicas
} }
@@ -92,6 +97,7 @@ func (h *ConsistentHash) AddWithWeight(node interface{}, weight int) {
h.AddWithReplicas(node, replicas) h.AddWithReplicas(node, replicas)
} }
// Get returns the corresponding node from h base on the given v.
func (h *ConsistentHash) Get(v interface{}) (interface{}, bool) { func (h *ConsistentHash) Get(v interface{}) (interface{}, bool) {
h.lock.RLock() h.lock.RLock()
defer h.lock.RUnlock() defer h.lock.RUnlock()
@@ -118,6 +124,7 @@ func (h *ConsistentHash) Get(v interface{}) (interface{}, bool) {
} }
} }
// Remove removes the given node from h.
func (h *ConsistentHash) Remove(node interface{}) { func (h *ConsistentHash) Remove(node interface{}) {
nodeRepr := repr(node) nodeRepr := repr(node)
@@ -133,7 +140,7 @@ func (h *ConsistentHash) Remove(node interface{}) {
index := sort.Search(len(h.keys), func(i int) bool { index := sort.Search(len(h.keys), func(i int) bool {
return h.keys[i] >= hash return h.keys[i] >= hash
}) })
if index < len(h.keys) { if index < len(h.keys) && h.keys[index] == hash {
h.keys = append(h.keys[:index], h.keys[index+1:]...) h.keys = append(h.keys[:index], h.keys[index+1:]...)
} }
h.removeRingNode(hash, nodeRepr) h.removeRingNode(hash, nodeRepr)

View File

@@ -132,8 +132,8 @@ func TestConsistentHash_RemoveInterface(t *testing.T) {
assert.Equal(t, 1, len(ch.nodes)) assert.Equal(t, 1, len(ch.nodes))
node, ok := ch.Get(1) node, ok := ch.Get(1)
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, key, node.(*MockNode).Addr) assert.Equal(t, key, node.(*mockNode).addr)
assert.Equal(t, 2, node.(*MockNode).Id) assert.Equal(t, 2, node.(*mockNode).id)
} }
func getKeysBeforeAndAfterFailure(t *testing.T, prefix string, index int) (map[int]string, map[int]string) { func getKeysBeforeAndAfterFailure(t *testing.T, prefix string, index int) (map[int]string, map[int]string) {
@@ -164,18 +164,18 @@ func getKeysBeforeAndAfterFailure(t *testing.T, prefix string, index int) (map[i
return keys, newKeys return keys, newKeys
} }
type MockNode struct { type mockNode struct {
Addr string addr string
Id int id int
} }
func newMockNode(addr string, id int) *MockNode { func newMockNode(addr string, id int) *mockNode {
return &MockNode{ return &mockNode{
Addr: addr, addr: addr,
Id: id, id: id,
} }
} }
func (n *MockNode) String() string { func (n *mockNode) String() string {
return n.Addr return n.addr
} }

View File

@@ -7,16 +7,19 @@ import (
"github.com/spaolacci/murmur3" "github.com/spaolacci/murmur3"
) )
// Hash returns the hash value of data.
func Hash(data []byte) uint64 { func Hash(data []byte) uint64 {
return murmur3.Sum64(data) return murmur3.Sum64(data)
} }
// Md5 returns the md5 bytes of data.
func Md5(data []byte) []byte { func Md5(data []byte) []byte {
digest := md5.New() digest := md5.New()
digest.Write(data) digest.Write(data)
return digest.Sum(nil) return digest.Sum(nil)
} }
// Md5Hex returns the md5 hex string of data.
func Md5Hex(data []byte) string { func Md5Hex(data []byte) string {
return fmt.Sprintf("%x", Md5(data)) return fmt.Sprintf("%x", Md5(data))
} }

View File

@@ -5,11 +5,13 @@ import (
"sync" "sync"
) )
// A BufferPool is a pool to buffer bytes.Buffer objects.
type BufferPool struct { type BufferPool struct {
capability int capability int
pool *sync.Pool pool *sync.Pool
} }
// NewBufferPool returns a BufferPool.
func NewBufferPool(capability int) *BufferPool { func NewBufferPool(capability int) *BufferPool {
return &BufferPool{ return &BufferPool{
capability: capability, capability: capability,
@@ -21,12 +23,14 @@ func NewBufferPool(capability int) *BufferPool {
} }
} }
// Get returns a bytes.Buffer object from bp.
func (bp *BufferPool) Get() *bytes.Buffer { func (bp *BufferPool) Get() *bytes.Buffer {
buf := bp.pool.Get().(*bytes.Buffer) buf := bp.pool.Get().(*bytes.Buffer)
buf.Reset() buf.Reset()
return buf return buf
} }
// Put returns buf into bp.
func (bp *BufferPool) Put(buf *bytes.Buffer) { func (bp *BufferPool) Put(buf *bytes.Buffer) {
if buf.Cap() < bp.capability { if buf.Cap() < bp.capability {
bp.pool.Put(buf) bp.pool.Put(buf)

View File

@@ -10,6 +10,7 @@ func (nopCloser) Close() error {
return nil return nil
} }
// NopCloser returns a io.WriteCloser that does nothing on calling Close.
func NopCloser(w io.Writer) io.WriteCloser { func NopCloser(w io.Writer) io.WriteCloser {
return nopCloser{w} return nopCloser{w}
} }

View File

@@ -16,9 +16,11 @@ type (
omitPrefix string omitPrefix string
} }
// TextReadOption defines the method to customize the text reading functions.
TextReadOption func(*textReadOptions) TextReadOption func(*textReadOptions)
) )
// DupReadCloser returns two io.ReadCloser that read from the first will be written to the second.
// The first returned reader needs to be read first, because the content // The first returned reader needs to be read first, because the content
// read from it will be written to the underlying buffer of the second reader. // read from it will be written to the underlying buffer of the second reader.
func DupReadCloser(reader io.ReadCloser) (io.ReadCloser, io.ReadCloser) { func DupReadCloser(reader io.ReadCloser) (io.ReadCloser, io.ReadCloser) {
@@ -27,6 +29,7 @@ func DupReadCloser(reader io.ReadCloser) (io.ReadCloser, io.ReadCloser) {
return ioutil.NopCloser(tee), ioutil.NopCloser(&buf) return ioutil.NopCloser(tee), ioutil.NopCloser(&buf)
} }
// KeepSpace customizes the reading functions to keep leading and tailing spaces.
func KeepSpace() TextReadOption { func KeepSpace() TextReadOption {
return func(o *textReadOptions) { return func(o *textReadOptions) {
o.keepSpace = true o.keepSpace = true
@@ -49,6 +52,7 @@ func ReadBytes(reader io.Reader, buf []byte) error {
return nil return nil
} }
// ReadText reads content from the given file with leading and tailing spaces trimmed.
func ReadText(filename string) (string, error) { func ReadText(filename string) (string, error) {
content, err := ioutil.ReadFile(filename) content, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
@@ -58,6 +62,7 @@ func ReadText(filename string) (string, error) {
return strings.TrimSpace(string(content)), nil return strings.TrimSpace(string(content)), nil
} }
// ReadTextLines reads the text lines from given file.
func ReadTextLines(filename string, opts ...TextReadOption) ([]string, error) { func ReadTextLines(filename string, opts ...TextReadOption) ([]string, error) {
var readOpts textReadOptions var readOpts textReadOptions
for _, opt := range opts { for _, opt := range opts {
@@ -90,12 +95,14 @@ func ReadTextLines(filename string, opts ...TextReadOption) ([]string, error) {
return lines, scanner.Err() return lines, scanner.Err()
} }
// WithoutBlank customizes the reading functions to ignore blank lines.
func WithoutBlank() TextReadOption { func WithoutBlank() TextReadOption {
return func(o *textReadOptions) { return func(o *textReadOptions) {
o.withoutBlanks = true o.withoutBlanks = true
} }
} }
// OmitWithPrefix customizes the reading functions to ignore the lines with given leading prefix.
func OmitWithPrefix(prefix string) TextReadOption { func OmitWithPrefix(prefix string) TextReadOption {
return func(o *textReadOptions) { return func(o *textReadOptions) {
o.omitPrefix = prefix o.omitPrefix = prefix

View File

@@ -8,6 +8,7 @@ import (
const bufSize = 32 * 1024 const bufSize = 32 * 1024
// CountLines returns the number of lines in file.
func CountLines(file string) (int, error) { func CountLines(file string) (int, error) {
f, err := os.Open(file) f, err := os.Open(file)
if err != nil { if err != nil {

View File

@@ -6,6 +6,7 @@ import (
"strings" "strings"
) )
// A TextLineScanner is a scanner that can scan lines from given reader.
type TextLineScanner struct { type TextLineScanner struct {
reader *bufio.Reader reader *bufio.Reader
hasNext bool hasNext bool
@@ -13,6 +14,7 @@ type TextLineScanner struct {
err error err error
} }
// NewTextLineScanner returns a TextLineScanner with given reader.
func NewTextLineScanner(reader io.Reader) *TextLineScanner { func NewTextLineScanner(reader io.Reader) *TextLineScanner {
return &TextLineScanner{ return &TextLineScanner{
reader: bufio.NewReader(reader), reader: bufio.NewReader(reader),
@@ -20,6 +22,7 @@ func NewTextLineScanner(reader io.Reader) *TextLineScanner {
} }
} }
// Scan checks if scanner has more lines to read.
func (scanner *TextLineScanner) Scan() bool { func (scanner *TextLineScanner) Scan() bool {
if !scanner.hasNext { if !scanner.hasNext {
return false return false
@@ -37,6 +40,7 @@ func (scanner *TextLineScanner) Scan() bool {
return true return true
} }
// Line returns the next available line.
func (scanner *TextLineScanner) Line() (string, error) { func (scanner *TextLineScanner) Line() (string, error) {
return scanner.line, scanner.err return scanner.line, scanner.err
} }

View File

@@ -7,32 +7,38 @@ import (
"github.com/globalsign/mgo/bson" "github.com/globalsign/mgo/bson"
) )
// MilliTime represents time.Time that works better with mongodb.
type MilliTime struct { type MilliTime struct {
time.Time time.Time
} }
// MarshalJSON marshals mt to json bytes.
func (mt MilliTime) MarshalJSON() ([]byte, error) { func (mt MilliTime) MarshalJSON() ([]byte, error) {
return json.Marshal(mt.Milli()) return json.Marshal(mt.Milli())
} }
// UnmarshalJSON unmarshals data into mt.
func (mt *MilliTime) UnmarshalJSON(data []byte) error { func (mt *MilliTime) UnmarshalJSON(data []byte) error {
var milli int64 var milli int64
if err := json.Unmarshal(data, &milli); err != nil { if err := json.Unmarshal(data, &milli); err != nil {
return err return err
} else {
mt.Time = time.Unix(0, milli*int64(time.Millisecond))
return nil
} }
mt.Time = time.Unix(0, milli*int64(time.Millisecond))
return nil
} }
// GetBSON returns BSON base on mt.
func (mt MilliTime) GetBSON() (interface{}, error) { func (mt MilliTime) GetBSON() (interface{}, error) {
return mt.Time, nil return mt.Time, nil
} }
// SetBSON sets raw into mt.
func (mt *MilliTime) SetBSON(raw bson.Raw) error { func (mt *MilliTime) SetBSON(raw bson.Raw) error {
return raw.Unmarshal(&mt.Time) return raw.Unmarshal(&mt.Time)
} }
// Milli returns milliseconds for mt.
func (mt MilliTime) Milli() int64 { func (mt MilliTime) Milli() int64 {
return mt.UnixNano() / int64(time.Millisecond) return mt.UnixNano() / int64(time.Millisecond)
} }

View File

@@ -8,10 +8,12 @@ import (
"strings" "strings"
) )
// Marshal marshals v into json bytes.
func Marshal(v interface{}) ([]byte, error) { func Marshal(v interface{}) ([]byte, error) {
return json.Marshal(v) return json.Marshal(v)
} }
// Unmarshal unmarshals data bytes into v.
func Unmarshal(data []byte, v interface{}) error { func Unmarshal(data []byte, v interface{}) error {
decoder := json.NewDecoder(bytes.NewReader(data)) decoder := json.NewDecoder(bytes.NewReader(data))
if err := unmarshalUseNumber(decoder, v); err != nil { if err := unmarshalUseNumber(decoder, v); err != nil {
@@ -21,6 +23,7 @@ func Unmarshal(data []byte, v interface{}) error {
return nil return nil
} }
// UnmarshalFromString unmarshals v from str.
func UnmarshalFromString(str string, v interface{}) error { func UnmarshalFromString(str string, v interface{}) error {
decoder := json.NewDecoder(strings.NewReader(str)) decoder := json.NewDecoder(strings.NewReader(str))
if err := unmarshalUseNumber(decoder, v); err != nil { if err := unmarshalUseNumber(decoder, v); err != nil {
@@ -30,6 +33,7 @@ func UnmarshalFromString(str string, v interface{}) error {
return nil return nil
} }
// UnmarshalFromReader unmarshals v from reader.
func UnmarshalFromReader(reader io.Reader, v interface{}) error { func UnmarshalFromReader(reader io.Reader, v interface{}) error {
var buf strings.Builder var buf strings.Builder
teeReader := io.TeeReader(reader, &buf) teeReader := io.TeeReader(reader, &buf)

View File

@@ -1,8 +1,11 @@
package lang package lang
// Placeholder is a placeholder object that can be used globally.
var Placeholder PlaceholderType var Placeholder PlaceholderType
type ( type (
GenericType = interface{} // GenericType can be used to hold any type.
GenericType = interface{}
// PlaceholderType represents a placeholder type.
PlaceholderType = struct{} PlaceholderType = struct{}
) )

View File

@@ -27,9 +27,13 @@ end`
) )
const ( const (
// Unknown means not initialized state.
Unknown = iota Unknown = iota
// Allowed means allowed state.
Allowed Allowed
// HitQuota means this request exactly hit the quota.
HitQuota HitQuota
// OverQuota means passed the quota.
OverQuota OverQuota
internalOverQuota = 0 internalOverQuota = 0
@@ -37,11 +41,14 @@ const (
internalHitQuota = 2 internalHitQuota = 2
) )
// ErrUnknownCode is an error that represents unknown status code.
var ErrUnknownCode = errors.New("unknown status code") var ErrUnknownCode = errors.New("unknown status code")
type ( type (
LimitOption func(l *PeriodLimit) // PeriodOption defines the method to customize a PeriodLimit.
PeriodOption func(l *PeriodLimit)
// A PeriodLimit is used to limit requests during a period of time.
PeriodLimit struct { PeriodLimit struct {
period int period int
quota int quota int
@@ -51,8 +58,9 @@ type (
} }
) )
// NewPeriodLimit returns a PeriodLimit with given parameters.
func NewPeriodLimit(period, quota int, limitStore *redis.Redis, keyPrefix string, func NewPeriodLimit(period, quota int, limitStore *redis.Redis, keyPrefix string,
opts ...LimitOption) *PeriodLimit { opts ...PeriodOption) *PeriodLimit {
limiter := &PeriodLimit{ limiter := &PeriodLimit{
period: period, period: period,
quota: quota, quota: quota,
@@ -67,6 +75,7 @@ func NewPeriodLimit(period, quota int, limitStore *redis.Redis, keyPrefix string
return limiter return limiter
} }
// Take requests a permit, it returns the permit state.
func (h *PeriodLimit) Take(key string) (int, error) { func (h *PeriodLimit) Take(key string) (int, error) {
resp, err := h.limitStore.Eval(periodScript, []string{h.keyPrefix + key}, []string{ resp, err := h.limitStore.Eval(periodScript, []string{h.keyPrefix + key}, []string{
strconv.Itoa(h.quota), strconv.Itoa(h.quota),
@@ -97,12 +106,13 @@ func (h *PeriodLimit) calcExpireSeconds() int {
if h.align { if h.align {
unix := time.Now().Unix() + zoneDiff unix := time.Now().Unix() + zoneDiff
return h.period - int(unix%int64(h.period)) return h.period - int(unix%int64(h.period))
} else {
return h.period
} }
return h.period
} }
func Align() LimitOption { // Align returns a func to customize a PeriodLimit with alignment.
func Align() PeriodOption {
return func(l *PeriodLimit) { return func(l *PeriodLimit) {
l.align = true l.align = true
} }

View File

@@ -33,7 +33,7 @@ func TestPeriodLimit_RedisUnavailable(t *testing.T) {
assert.Equal(t, 0, val) assert.Equal(t, 0, val)
} }
func testPeriodLimit(t *testing.T, opts ...LimitOption) { func testPeriodLimit(t *testing.T, opts ...PeriodOption) {
store, clean, err := redistest.CreateRedis() store, clean, err := redistest.CreateRedis()
assert.Nil(t, err) assert.Nil(t, err)
defer clean() defer clean()

View File

@@ -26,6 +26,7 @@ const (
) )
var ( var (
// ErrServiceOverloaded is returned by Shedder.Allow when the service is overloaded.
ErrServiceOverloaded = errors.New("service overloaded") ErrServiceOverloaded = errors.New("service overloaded")
// default to be enabled // default to be enabled
@@ -37,15 +38,22 @@ var (
) )
type ( type (
// A Promise interface is returned by Shedder.Allow to let callers tell
// whether the processing request is successful or not.
Promise interface { Promise interface {
// Pass lets the caller tell that the call is successful.
Pass() Pass()
// Fail lets the caller tell that the call is failed.
Fail() Fail()
} }
// Shedder is the interface that wraps the Allow method.
Shedder interface { Shedder interface {
// Allow returns the Promise if allowed, otherwise ErrServiceOverloaded.
Allow() (Promise, error) Allow() (Promise, error)
} }
// ShedderOption lets caller customize the Shedder.
ShedderOption func(opts *shedderOptions) ShedderOption func(opts *shedderOptions)
shedderOptions struct { shedderOptions struct {
@@ -67,10 +75,13 @@ type (
} }
) )
// Disable lets callers disable load shedding.
func Disable() { func Disable() {
enabled.Set(false) enabled.Set(false)
} }
// NewAdaptiveShedder returns an adaptive shedder.
// opts can be used to customize the Shedder.
func NewAdaptiveShedder(opts ...ShedderOption) Shedder { func NewAdaptiveShedder(opts ...ShedderOption) Shedder {
if !enabled.True() { if !enabled.True() {
return newNopShedder() return newNopShedder()
@@ -97,6 +108,7 @@ func NewAdaptiveShedder(opts ...ShedderOption) Shedder {
} }
} }
// Allow implements Shedder.Allow.
func (as *adaptiveShedder) Allow() (Promise, error) { func (as *adaptiveShedder) Allow() (Promise, error) {
if as.shouldDrop() { if as.shouldDrop() {
as.dropTime.Set(timex.Now()) as.dropTime.Set(timex.Now())
@@ -156,7 +168,7 @@ func (as *adaptiveShedder) maxPass() int64 {
} }
func (as *adaptiveShedder) minRt() float64 { func (as *adaptiveShedder) minRt() float64 {
var result = defaultMinRt result := defaultMinRt
as.rtCounter.Reduce(func(b *collection.Bucket) { as.rtCounter.Reduce(func(b *collection.Bucket) {
if b.Count <= 0 { if b.Count <= 0 {
@@ -213,18 +225,21 @@ func (as *adaptiveShedder) systemOverloaded() bool {
return systemOverloadChecker(as.cpuThreshold) return systemOverloadChecker(as.cpuThreshold)
} }
// WithBuckets customizes the Shedder with given number of buckets.
func WithBuckets(buckets int) ShedderOption { func WithBuckets(buckets int) ShedderOption {
return func(opts *shedderOptions) { return func(opts *shedderOptions) {
opts.buckets = buckets opts.buckets = buckets
} }
} }
// WithCpuThreshold customizes the Shedder with given cpu threshold.
func WithCpuThreshold(threshold int64) ShedderOption { func WithCpuThreshold(threshold int64) ShedderOption {
return func(opts *shedderOptions) { return func(opts *shedderOptions) {
opts.cpuThreshold = threshold opts.cpuThreshold = threshold
} }
} }
// WithWindow customizes the Shedder with given
func WithWindow(window time.Duration) ShedderOption { func WithWindow(window time.Duration) ShedderOption {
return func(opts *shedderOptions) { return func(opts *shedderOptions) {
opts.window = window opts.window = window

View File

@@ -201,7 +201,7 @@ func BenchmarkAdaptiveShedder_Allow(b *testing.B) {
logx.Disable() logx.Disable()
bench := func(b *testing.B) { bench := func(b *testing.B) {
var shedder = NewAdaptiveShedder() shedder := NewAdaptiveShedder()
proba := mathx.NewProba() proba := mathx.NewProba()
for i := 0; i < 6000; i++ { for i := 0; i < 6000; i++ {
p, err := shedder.Allow() p, err := shedder.Allow()

View File

@@ -1,7 +1,6 @@
package load package load
type nopShedder struct { type nopShedder struct{}
}
func newNopShedder() Shedder { func newNopShedder() Shedder {
return nopShedder{} return nopShedder{}
@@ -11,8 +10,7 @@ func (s nopShedder) Allow() (Promise, error) {
return nopPromise{}, nil return nopPromise{}, nil
} }
type nopPromise struct { type nopPromise struct{}
}
func (p nopPromise) Pass() { func (p nopPromise) Pass() {
} }

View File

@@ -6,11 +6,13 @@ import (
"github.com/tal-tech/go-zero/core/syncx" "github.com/tal-tech/go-zero/core/syncx"
) )
// A ShedderGroup is a manager to manage key based shedders.
type ShedderGroup struct { type ShedderGroup struct {
options []ShedderOption options []ShedderOption
manager *syncx.ResourceManager manager *syncx.ResourceManager
} }
// NewShedderGroup returns a ShedderGroup.
func NewShedderGroup(opts ...ShedderOption) *ShedderGroup { func NewShedderGroup(opts ...ShedderOption) *ShedderGroup {
return &ShedderGroup{ return &ShedderGroup{
options: opts, options: opts,
@@ -18,6 +20,7 @@ func NewShedderGroup(opts ...ShedderOption) *ShedderGroup {
} }
} }
// GetShedder gets the Shedder for the given key.
func (g *ShedderGroup) GetShedder(key string) Shedder { func (g *ShedderGroup) GetShedder(key string) Shedder {
shedder, _ := g.manager.GetResource(key, func() (closer io.Closer, e error) { shedder, _ := g.manager.GetResource(key, func() (closer io.Closer, e error) {
return nopCloser{ return nopCloser{

View File

@@ -9,6 +9,7 @@ import (
) )
type ( type (
// A SheddingStat is used to store the statistics for load shedding.
SheddingStat struct { SheddingStat struct {
name string name string
total int64 total int64
@@ -23,6 +24,7 @@ type (
} }
) )
// NewSheddingStat returns a SheddingStat.
func NewSheddingStat(name string) *SheddingStat { func NewSheddingStat(name string) *SheddingStat {
st := &SheddingStat{ st := &SheddingStat{
name: name, name: name,
@@ -31,14 +33,17 @@ func NewSheddingStat(name string) *SheddingStat {
return st return st
} }
// IncrementTotal increments the total requests.
func (s *SheddingStat) IncrementTotal() { func (s *SheddingStat) IncrementTotal() {
atomic.AddInt64(&s.total, 1) atomic.AddInt64(&s.total, 1)
} }
// IncrementPass increments the passed requests.
func (s *SheddingStat) IncrementPass() { func (s *SheddingStat) IncrementPass() {
atomic.AddInt64(&s.pass, 1) atomic.AddInt64(&s.pass, 1)
} }
// IncrementDrop increments the dropped requests.
func (s *SheddingStat) IncrementDrop() { func (s *SheddingStat) IncrementDrop() {
atomic.AddInt64(&s.drop, 1) atomic.AddInt64(&s.drop, 1)
} }

View File

@@ -1,8 +1,10 @@
package logx package logx
// A LogConf is a logging config.
type LogConf struct { type LogConf struct {
ServiceName string `json:",optional"` ServiceName string `json:",optional"`
Mode string `json:",default=console,options=console|file|volume"` Mode string `json:",default=console,options=console|file|volume"`
TimeFormat string `json:",optional"`
Path string `json:",default=logs"` Path string `json:",default=logs"`
Level string `json:",default=info,options=info|error|severe"` Level string `json:",default=info,options=info|error|severe"`
Compress bool `json:",optional"` Compress bool `json:",optional"`

View File

@@ -12,6 +12,7 @@ const durationCallerDepth = 3
type durationLogger logEntry type durationLogger logEntry
// WithDuration returns a Logger which logs the given duration.
func WithDuration(d time.Duration) Logger { func WithDuration(d time.Duration) Logger {
return &durationLogger{ return &durationLogger{
Duration: timex.ReprOfDuration(d), Duration: timex.ReprOfDuration(d),

View File

@@ -1,21 +1,25 @@
package logx package logx
// A LessLogger is a logger that control to log once during the given duration.
type LessLogger struct { type LessLogger struct {
*limitedExecutor *limitedExecutor
} }
// NewLessLogger returns a LessLogger.
func NewLessLogger(milliseconds int) *LessLogger { func NewLessLogger(milliseconds int) *LessLogger {
return &LessLogger{ return &LessLogger{
limitedExecutor: newLimitedExecutor(milliseconds), limitedExecutor: newLimitedExecutor(milliseconds),
} }
} }
// Error logs v into error log or discard it if more than once in the given duration.
func (logger *LessLogger) Error(v ...interface{}) { func (logger *LessLogger) Error(v ...interface{}) {
logger.logOrDiscard(func() { logger.logOrDiscard(func() {
Error(v...) Error(v...)
}) })
} }
// Errorf logs v with format into error log or discard it if more than once in the given duration.
func (logger *LessLogger) Errorf(format string, v ...interface{}) { func (logger *LessLogger) Errorf(format string, v ...interface{}) {
logger.logOrDiscard(func() { logger.logOrDiscard(func() {
Errorf(format, v...) Errorf(format, v...)

View File

@@ -7,7 +7,7 @@ type lessWriter struct {
writer io.Writer writer io.Writer
} }
func NewLessWriter(writer io.Writer, milliseconds int) *lessWriter { func newLessWriter(writer io.Writer, milliseconds int) *lessWriter {
return &lessWriter{ return &lessWriter{
limitedExecutor: newLimitedExecutor(milliseconds), limitedExecutor: newLimitedExecutor(milliseconds),
writer: writer, writer: writer,

View File

@@ -9,7 +9,7 @@ import (
func TestLessWriter(t *testing.T) { func TestLessWriter(t *testing.T) {
var builder strings.Builder var builder strings.Builder
w := NewLessWriter(&builder, 500) w := newLessWriter(&builder, 500)
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
_, err := w.Write([]byte("hello")) _, err := w.Write([]byte("hello"))
assert.Nil(t, err) assert.Nil(t, err)

View File

@@ -32,8 +32,6 @@ const (
) )
const ( const (
timeFormat = "2006-01-02T15:04:05.000Z07"
accessFilename = "access.log" accessFilename = "access.log"
errorFilename = "error.log" errorFilename = "error.log"
severeFilename = "severe.log" severeFilename = "severe.log"
@@ -57,10 +55,14 @@ const (
) )
var ( var (
ErrLogPathNotSet = errors.New("log path must be set") // ErrLogPathNotSet is an error that indicates the log path is not set.
ErrLogNotInitialized = errors.New("log not initialized") ErrLogPathNotSet = errors.New("log path must be set")
// ErrLogNotInitialized is an error that log is not initialized.
ErrLogNotInitialized = errors.New("log not initialized")
// ErrLogServiceNameNotSet is an error that indicates that the service name is not set.
ErrLogServiceNameNotSet = errors.New("log service name must be set") ErrLogServiceNameNotSet = errors.New("log service name must be set")
timeFormat = "2006-01-02T15:04:05.000Z07"
writeConsole bool writeConsole bool
logLevel uint32 logLevel uint32
infoLog io.WriteCloser infoLog io.WriteCloser
@@ -89,8 +91,10 @@ type (
keepDays int keepDays int
} }
// LogOption defines the method to customize the logging.
LogOption func(options *logOptions) LogOption func(options *logOptions)
// A Logger represents a logger.
Logger interface { Logger interface {
Error(...interface{}) Error(...interface{})
Errorf(string, ...interface{}) Errorf(string, ...interface{})
@@ -102,6 +106,7 @@ type (
} }
) )
// MustSetup sets up logging with given config c. It exits on error.
func MustSetup(c LogConf) { func MustSetup(c LogConf) {
Must(SetUp(c)) Must(SetUp(c))
} }
@@ -111,6 +116,10 @@ func MustSetup(c LogConf) {
// we need to allow different service frameworks to initialize logx respectively. // we need to allow different service frameworks to initialize logx respectively.
// the same logic for SetUp // the same logic for SetUp
func SetUp(c LogConf) error { func SetUp(c LogConf) error {
if len(c.TimeFormat) > 0 {
timeFormat = c.TimeFormat
}
switch c.Mode { switch c.Mode {
case consoleMode: case consoleMode:
setupWithConsole(c) setupWithConsole(c)
@@ -122,10 +131,12 @@ func SetUp(c LogConf) error {
} }
} }
// Alert alerts v in alert level, and the message is written to error log.
func Alert(v string) { func Alert(v string) {
output(errorLog, levelAlert, v) output(errorLog, levelAlert, v)
} }
// Close closes the logging.
func Close() error { func Close() error {
if writeConsole { if writeConsole {
return nil return nil
@@ -170,6 +181,7 @@ func Close() error {
return nil return nil
} }
// Disable disables the logging.
func Disable() { func Disable() {
once.Do(func() { once.Do(func() {
atomic.StoreUint32(&initialized, 1) atomic.StoreUint32(&initialized, 1)
@@ -183,40 +195,49 @@ func Disable() {
}) })
} }
// Error writes v into error log.
func Error(v ...interface{}) { func Error(v ...interface{}) {
ErrorCaller(1, v...) ErrorCaller(1, v...)
} }
// Errorf writes v with format into error log.
func Errorf(format string, v ...interface{}) { func Errorf(format string, v ...interface{}) {
ErrorCallerf(1, format, v...) ErrorCallerf(1, format, v...)
} }
// ErrorCaller writes v with context into error log.
func ErrorCaller(callDepth int, v ...interface{}) { func ErrorCaller(callDepth int, v ...interface{}) {
errorSync(fmt.Sprint(v...), callDepth+callerInnerDepth) errorSync(fmt.Sprint(v...), callDepth+callerInnerDepth)
} }
// ErrorCallerf writes v with context in format into error log.
func ErrorCallerf(callDepth int, format string, v ...interface{}) { func ErrorCallerf(callDepth int, format string, v ...interface{}) {
errorSync(fmt.Sprintf(format, v...), callDepth+callerInnerDepth) errorSync(fmt.Sprintf(format, v...), callDepth+callerInnerDepth)
} }
// ErrorStack writes v along with call stack into error log.
func ErrorStack(v ...interface{}) { func ErrorStack(v ...interface{}) {
// there is newline in stack string // there is newline in stack string
stackSync(fmt.Sprint(v...)) stackSync(fmt.Sprint(v...))
} }
// ErrorStackf writes v along with call stack in format into error log.
func ErrorStackf(format string, v ...interface{}) { func ErrorStackf(format string, v ...interface{}) {
// there is newline in stack string // there is newline in stack string
stackSync(fmt.Sprintf(format, v...)) stackSync(fmt.Sprintf(format, v...))
} }
// Info writes v into access log.
func Info(v ...interface{}) { func Info(v ...interface{}) {
infoSync(fmt.Sprint(v...)) infoSync(fmt.Sprint(v...))
} }
// Infof writes v with format into access log.
func Infof(format string, v ...interface{}) { func Infof(format string, v ...interface{}) {
infoSync(fmt.Sprintf(format, v...)) infoSync(fmt.Sprintf(format, v...))
} }
// Must checks if err is nil, otherwise logs the err and exits.
func Must(err error) { func Must(err error) {
if err != nil { if err != nil {
msg := formatWithCaller(err.Error(), 3) msg := formatWithCaller(err.Error(), 3)
@@ -226,46 +247,56 @@ func Must(err error) {
} }
} }
// SetLevel sets the logging level. It can be used to suppress some logs.
func SetLevel(level uint32) { func SetLevel(level uint32) {
atomic.StoreUint32(&logLevel, level) atomic.StoreUint32(&logLevel, level)
} }
// Severe writes v into severe log.
func Severe(v ...interface{}) { func Severe(v ...interface{}) {
severeSync(fmt.Sprint(v...)) severeSync(fmt.Sprint(v...))
} }
// Severef writes v with format into severe log.
func Severef(format string, v ...interface{}) { func Severef(format string, v ...interface{}) {
severeSync(fmt.Sprintf(format, v...)) severeSync(fmt.Sprintf(format, v...))
} }
// Slow writes v into slow log.
func Slow(v ...interface{}) { func Slow(v ...interface{}) {
slowSync(fmt.Sprint(v...)) slowSync(fmt.Sprint(v...))
} }
// Slowf writes v with format into slow log.
func Slowf(format string, v ...interface{}) { func Slowf(format string, v ...interface{}) {
slowSync(fmt.Sprintf(format, v...)) slowSync(fmt.Sprintf(format, v...))
} }
// Stat writes v into stat log.
func Stat(v ...interface{}) { func Stat(v ...interface{}) {
statSync(fmt.Sprint(v...)) statSync(fmt.Sprint(v...))
} }
// Statf writes v with format into stat log.
func Statf(format string, v ...interface{}) { func Statf(format string, v ...interface{}) {
statSync(fmt.Sprintf(format, v...)) statSync(fmt.Sprintf(format, v...))
} }
// WithCooldownMillis customizes logging on writing call stack interval.
func WithCooldownMillis(millis int) LogOption { func WithCooldownMillis(millis int) LogOption {
return func(opts *logOptions) { return func(opts *logOptions) {
opts.logStackCooldownMills = millis opts.logStackCooldownMills = millis
} }
} }
// WithKeepDays customizes logging to keep logs with days.
func WithKeepDays(days int) LogOption { func WithKeepDays(days int) LogOption {
return func(opts *logOptions) { return func(opts *logOptions) {
opts.keepDays = days opts.keepDays = days
} }
} }
// WithGzip customizes logging to automatically gzip the log files.
func WithGzip() LogOption { func WithGzip() LogOption {
return func(opts *logOptions) { return func(opts *logOptions) {
opts.gzipEnabled = true opts.gzipEnabled = true
@@ -382,7 +413,7 @@ func setupWithConsole(c LogConf) {
errorLog = newLogWriter(log.New(os.Stderr, "", flags)) errorLog = newLogWriter(log.New(os.Stderr, "", flags))
severeLog = newLogWriter(log.New(os.Stderr, "", flags)) severeLog = newLogWriter(log.New(os.Stderr, "", flags))
slowLog = newLogWriter(log.New(os.Stderr, "", flags)) slowLog = newLogWriter(log.New(os.Stderr, "", flags))
stackLog = NewLessWriter(errorLog, options.logStackCooldownMills) stackLog = newLessWriter(errorLog, options.logStackCooldownMills)
statLog = infoLog statLog = infoLog
}) })
} }
@@ -434,7 +465,7 @@ func setupWithFiles(c LogConf) error {
return return
} }
stackLog = NewLessWriter(errorLog, options.logStackCooldownMills) stackLog = newLessWriter(errorLog, options.logStackCooldownMills)
}) })
return err return err

View File

@@ -22,13 +22,15 @@ const (
dateFormat = "2006-01-02" dateFormat = "2006-01-02"
hoursPerDay = 24 hoursPerDay = 24
bufferSize = 100 bufferSize = 100
defaultDirMode = 0755 defaultDirMode = 0o755
defaultFileMode = 0600 defaultFileMode = 0o600
) )
// ErrLogFileClosed is an error that indicates the log file is already closed.
var ErrLogFileClosed = errors.New("error: log file closed") var ErrLogFileClosed = errors.New("error: log file closed")
type ( type (
// A RotateRule interface is used to define the log rotating rules.
RotateRule interface { RotateRule interface {
BackupFileName() string BackupFileName() string
MarkRotated() MarkRotated()
@@ -36,6 +38,7 @@ type (
ShallRotate() bool ShallRotate() bool
} }
// A RotateLogger is a Logger that can rotate log files with given rules.
RotateLogger struct { RotateLogger struct {
filename string filename string
backup string backup string
@@ -50,6 +53,7 @@ type (
closeOnce sync.Once closeOnce sync.Once
} }
// A DailyRotateRule is a rule to daily rotate the log files.
DailyRotateRule struct { DailyRotateRule struct {
rotatedTime string rotatedTime string
filename string filename string
@@ -59,6 +63,7 @@ type (
} }
) )
// DefaultRotateRule is a default log rotating rule, currently DailyRotateRule.
func DefaultRotateRule(filename, delimiter string, days int, gzip bool) RotateRule { func DefaultRotateRule(filename, delimiter string, days int, gzip bool) RotateRule {
return &DailyRotateRule{ return &DailyRotateRule{
rotatedTime: getNowDate(), rotatedTime: getNowDate(),
@@ -69,14 +74,17 @@ func DefaultRotateRule(filename, delimiter string, days int, gzip bool) RotateRu
} }
} }
// BackupFileName returns the backup filename on rotating.
func (r *DailyRotateRule) BackupFileName() string { func (r *DailyRotateRule) BackupFileName() string {
return fmt.Sprintf("%s%s%s", r.filename, r.delimiter, getNowDate()) return fmt.Sprintf("%s%s%s", r.filename, r.delimiter, getNowDate())
} }
// MarkRotated marks the rotated time of r to be the current time.
func (r *DailyRotateRule) MarkRotated() { func (r *DailyRotateRule) MarkRotated() {
r.rotatedTime = getNowDate() r.rotatedTime = getNowDate()
} }
// OutdatedFiles returns the files that exceeded the keeping days.
func (r *DailyRotateRule) OutdatedFiles() []string { func (r *DailyRotateRule) OutdatedFiles() []string {
if r.days <= 0 { if r.days <= 0 {
return nil return nil
@@ -113,10 +121,12 @@ func (r *DailyRotateRule) OutdatedFiles() []string {
return outdates return outdates
} }
// ShallRotate checks if the file should be rotated.
func (r *DailyRotateRule) ShallRotate() bool { func (r *DailyRotateRule) ShallRotate() bool {
return len(r.rotatedTime) > 0 && getNowDate() != r.rotatedTime return len(r.rotatedTime) > 0 && getNowDate() != r.rotatedTime
} }
// NewLogger returns a RotateLogger with given filename and rule, etc.
func NewLogger(filename string, rule RotateRule, compress bool) (*RotateLogger, error) { func NewLogger(filename string, rule RotateRule, compress bool) (*RotateLogger, error) {
l := &RotateLogger{ l := &RotateLogger{
filename: filename, filename: filename,
@@ -133,6 +143,7 @@ func NewLogger(filename string, rule RotateRule, compress bool) (*RotateLogger,
return l, nil return l, nil
} }
// Close closes l.
func (l *RotateLogger) Close() error { func (l *RotateLogger) Close() error {
var err error var err error
@@ -163,9 +174,9 @@ func (l *RotateLogger) Write(data []byte) (int, error) {
func (l *RotateLogger) getBackupFilename() string { func (l *RotateLogger) getBackupFilename() string {
if len(l.backup) == 0 { if len(l.backup) == 0 {
return l.rule.BackupFileName() return l.rule.BackupFileName()
} else {
return l.backup
} }
return l.backup
} }
func (l *RotateLogger) init() error { func (l *RotateLogger) init() error {

View File

@@ -67,6 +67,7 @@ func (l *traceLogger) write(writer io.Writer, level, content string) {
outputJson(writer, l) outputJson(writer, l)
} }
// WithContext sets ctx to log, for keeping tracing information.
func WithContext(ctx context.Context) Logger { func WithContext(ctx context.Context) Logger {
return &traceLogger{ return &traceLogger{
ctx: ctx, ctx: ctx,

View File

@@ -13,18 +13,19 @@ import (
) )
const ( const (
mockTraceId = "mock-trace-id" mockTraceID = "mock-trace-id"
mockSpanId = "mock-span-id" mockSpanID = "mock-span-id"
) )
var mock tracespec.Trace = new(mockTrace) var mock tracespec.Trace = new(mockTrace)
func TestTraceLog(t *testing.T) { func TestTraceLog(t *testing.T) {
var buf mockWriter var buf mockWriter
atomic.StoreUint32(&initialized, 1)
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock) ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
WithContext(ctx).(*traceLogger).write(&buf, levelInfo, testlog) WithContext(ctx).(*traceLogger).write(&buf, levelInfo, testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId)) assert.True(t, strings.Contains(buf.String(), mockTraceID))
assert.True(t, strings.Contains(buf.String(), mockSpanId)) assert.True(t, strings.Contains(buf.String(), mockSpanID))
} }
func TestTraceError(t *testing.T) { func TestTraceError(t *testing.T) {
@@ -35,12 +36,12 @@ func TestTraceError(t *testing.T) {
l := WithContext(ctx).(*traceLogger) l := WithContext(ctx).(*traceLogger)
SetLevel(InfoLevel) SetLevel(InfoLevel)
l.WithDuration(time.Second).Error(testlog) l.WithDuration(time.Second).Error(testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId)) assert.True(t, strings.Contains(buf.String(), mockTraceID))
assert.True(t, strings.Contains(buf.String(), mockSpanId)) assert.True(t, strings.Contains(buf.String(), mockSpanID))
buf.Reset() buf.Reset()
l.WithDuration(time.Second).Errorf(testlog) l.WithDuration(time.Second).Errorf(testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId)) assert.True(t, strings.Contains(buf.String(), mockTraceID))
assert.True(t, strings.Contains(buf.String(), mockSpanId)) assert.True(t, strings.Contains(buf.String(), mockSpanID))
} }
func TestTraceInfo(t *testing.T) { func TestTraceInfo(t *testing.T) {
@@ -51,12 +52,12 @@ func TestTraceInfo(t *testing.T) {
l := WithContext(ctx).(*traceLogger) l := WithContext(ctx).(*traceLogger)
SetLevel(InfoLevel) SetLevel(InfoLevel)
l.WithDuration(time.Second).Info(testlog) l.WithDuration(time.Second).Info(testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId)) assert.True(t, strings.Contains(buf.String(), mockTraceID))
assert.True(t, strings.Contains(buf.String(), mockSpanId)) assert.True(t, strings.Contains(buf.String(), mockSpanID))
buf.Reset() buf.Reset()
l.WithDuration(time.Second).Infof(testlog) l.WithDuration(time.Second).Infof(testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId)) assert.True(t, strings.Contains(buf.String(), mockTraceID))
assert.True(t, strings.Contains(buf.String(), mockSpanId)) assert.True(t, strings.Contains(buf.String(), mockSpanID))
} }
func TestTraceSlow(t *testing.T) { func TestTraceSlow(t *testing.T) {
@@ -67,12 +68,12 @@ func TestTraceSlow(t *testing.T) {
l := WithContext(ctx).(*traceLogger) l := WithContext(ctx).(*traceLogger)
SetLevel(InfoLevel) SetLevel(InfoLevel)
l.WithDuration(time.Second).Slow(testlog) l.WithDuration(time.Second).Slow(testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId)) assert.True(t, strings.Contains(buf.String(), mockTraceID))
assert.True(t, strings.Contains(buf.String(), mockSpanId)) assert.True(t, strings.Contains(buf.String(), mockSpanID))
buf.Reset() buf.Reset()
l.WithDuration(time.Second).Slowf(testlog) l.WithDuration(time.Second).Slowf(testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId)) assert.True(t, strings.Contains(buf.String(), mockTraceID))
assert.True(t, strings.Contains(buf.String(), mockSpanId)) assert.True(t, strings.Contains(buf.String(), mockSpanID))
} }
func TestTraceWithoutContext(t *testing.T) { func TestTraceWithoutContext(t *testing.T) {
@@ -82,22 +83,22 @@ func TestTraceWithoutContext(t *testing.T) {
l := WithContext(context.Background()).(*traceLogger) l := WithContext(context.Background()).(*traceLogger)
SetLevel(InfoLevel) SetLevel(InfoLevel)
l.WithDuration(time.Second).Info(testlog) l.WithDuration(time.Second).Info(testlog)
assert.False(t, strings.Contains(buf.String(), mockTraceId)) assert.False(t, strings.Contains(buf.String(), mockTraceID))
assert.False(t, strings.Contains(buf.String(), mockSpanId)) assert.False(t, strings.Contains(buf.String(), mockSpanID))
buf.Reset() buf.Reset()
l.WithDuration(time.Second).Infof(testlog) l.WithDuration(time.Second).Infof(testlog)
assert.False(t, strings.Contains(buf.String(), mockTraceId)) assert.False(t, strings.Contains(buf.String(), mockTraceID))
assert.False(t, strings.Contains(buf.String(), mockSpanId)) assert.False(t, strings.Contains(buf.String(), mockSpanID))
} }
type mockTrace struct{} type mockTrace struct{}
func (t mockTrace) TraceId() string { func (t mockTrace) TraceId() string {
return mockTraceId return mockTraceID
} }
func (t mockTrace) SpanId() string { func (t mockTrace) SpanId() string {
return mockSpanId return mockSpanID
} }
func (t mockTrace) Finish() { func (t mockTrace) Finish() {

View File

@@ -35,9 +35,9 @@ func (o *fieldOptionsWithContext) fromString() bool {
func (o *fieldOptionsWithContext) getDefault() (string, bool) { func (o *fieldOptionsWithContext) getDefault() (string, bool) {
if o == nil { if o == nil {
return "", false return "", false
} else {
return o.Default, len(o.Default) > 0
} }
return o.Default, len(o.Default) > 0
} }
func (o *fieldOptionsWithContext) optional() bool { func (o *fieldOptionsWithContext) optional() bool {
@@ -55,9 +55,9 @@ func (o *fieldOptionsWithContext) options() []string {
func (o *fieldOptions) optionalDep() string { func (o *fieldOptions) optionalDep() string {
if o == nil { if o == nil {
return "" return ""
} else {
return o.OptionalDep
} }
return o.OptionalDep
} }
func (o *fieldOptions) toOptionsWithContext(key string, m Valuer, fullName string) ( func (o *fieldOptions) toOptionsWithContext(key string, m Valuer, fullName string) (
@@ -77,29 +77,29 @@ func (o *fieldOptions) toOptionsWithContext(key string, m Valuer, fullName strin
_, selfOn := m.Value(key) _, selfOn := m.Value(key)
if baseOn == selfOn { if baseOn == selfOn {
return nil, fmt.Errorf("set value for either %q or %q in %q", dep, key, fullName) return nil, fmt.Errorf("set value for either %q or %q in %q", dep, key, fullName)
} else {
optional = baseOn
} }
optional = baseOn
} else { } else {
_, baseOn := m.Value(dep) _, baseOn := m.Value(dep)
_, selfOn := m.Value(key) _, selfOn := m.Value(key)
if baseOn != selfOn { if baseOn != selfOn {
return nil, fmt.Errorf("values for %q and %q should be both provided or both not in %q", return nil, fmt.Errorf("values for %q and %q should be both provided or both not in %q",
dep, key, fullName) dep, key, fullName)
} else {
optional = !baseOn
} }
optional = !baseOn
} }
} }
if o.fieldOptionsWithContext.Optional == optional { if o.fieldOptionsWithContext.Optional == optional {
return &o.fieldOptionsWithContext, nil return &o.fieldOptionsWithContext, nil
} else {
return &fieldOptionsWithContext{
FromString: o.FromString,
Optional: optional,
Options: o.Options,
Default: o.Default,
}, nil
} }
return &fieldOptionsWithContext{
FromString: o.FromString,
Optional: optional,
Options: o.Options,
Default: o.Default,
}, nil
} }

View File

@@ -10,10 +10,12 @@ const jsonTagKey = "json"
var jsonUnmarshaler = NewUnmarshaler(jsonTagKey) var jsonUnmarshaler = NewUnmarshaler(jsonTagKey)
// UnmarshalJsonBytes unmarshals content into v.
func UnmarshalJsonBytes(content []byte, v interface{}) error { func UnmarshalJsonBytes(content []byte, v interface{}) error {
return unmarshalJsonBytes(content, v, jsonUnmarshaler) return unmarshalJsonBytes(content, v, jsonUnmarshaler)
} }
// UnmarshalJsonReader unmarshals content from reader into v.
func UnmarshalJsonReader(reader io.Reader, v interface{}) error { func UnmarshalJsonReader(reader io.Reader, v interface{}) error {
return unmarshalJsonReader(reader, v, jsonUnmarshaler) return unmarshalJsonReader(reader, v, jsonUnmarshaler)
} }

View File

@@ -485,41 +485,41 @@ func TestUnmarshalBytesMap(t *testing.T) {
func TestUnmarshalBytesMapStruct(t *testing.T) { func TestUnmarshalBytesMapStruct(t *testing.T) {
var c struct { var c struct {
Persons map[string]struct { Persons map[string]struct {
Id int ID int
Name string `json:"name,optional"` Name string `json:"name,optional"`
} }
} }
content := []byte(`{"Persons": {"first": {"Id": 1, "name": "kevin"}}}`) content := []byte(`{"Persons": {"first": {"ID": 1, "name": "kevin"}}}`)
assert.Nil(t, UnmarshalJsonBytes(content, &c)) assert.Nil(t, UnmarshalJsonBytes(content, &c))
assert.Equal(t, 1, len(c.Persons)) assert.Equal(t, 1, len(c.Persons))
assert.Equal(t, 1, c.Persons["first"].Id) assert.Equal(t, 1, c.Persons["first"].ID)
assert.Equal(t, "kevin", c.Persons["first"].Name) assert.Equal(t, "kevin", c.Persons["first"].Name)
} }
func TestUnmarshalBytesMapStructPtr(t *testing.T) { func TestUnmarshalBytesMapStructPtr(t *testing.T) {
var c struct { var c struct {
Persons map[string]*struct { Persons map[string]*struct {
Id int ID int
Name string `json:"name,optional"` Name string `json:"name,optional"`
} }
} }
content := []byte(`{"Persons": {"first": {"Id": 1, "name": "kevin"}}}`) content := []byte(`{"Persons": {"first": {"ID": 1, "name": "kevin"}}}`)
assert.Nil(t, UnmarshalJsonBytes(content, &c)) assert.Nil(t, UnmarshalJsonBytes(content, &c))
assert.Equal(t, 1, len(c.Persons)) assert.Equal(t, 1, len(c.Persons))
assert.Equal(t, 1, c.Persons["first"].Id) assert.Equal(t, 1, c.Persons["first"].ID)
assert.Equal(t, "kevin", c.Persons["first"].Name) assert.Equal(t, "kevin", c.Persons["first"].Name)
} }
func TestUnmarshalBytesMapStructMissingPartial(t *testing.T) { func TestUnmarshalBytesMapStructMissingPartial(t *testing.T) {
var c struct { var c struct {
Persons map[string]*struct { Persons map[string]*struct {
Id int ID int
Name string Name string
} }
} }
content := []byte(`{"Persons": {"first": {"Id": 1}}}`) content := []byte(`{"Persons": {"first": {"ID": 1}}}`)
assert.NotNil(t, UnmarshalJsonBytes(content, &c)) assert.NotNil(t, UnmarshalJsonBytes(content, &c))
} }
@@ -527,21 +527,21 @@ func TestUnmarshalBytesMapStructMissingPartial(t *testing.T) {
func TestUnmarshalBytesMapStructOptional(t *testing.T) { func TestUnmarshalBytesMapStructOptional(t *testing.T) {
var c struct { var c struct {
Persons map[string]*struct { Persons map[string]*struct {
Id int ID int
Name string `json:"name,optional"` Name string `json:"name,optional"`
} }
} }
content := []byte(`{"Persons": {"first": {"Id": 1}}}`) content := []byte(`{"Persons": {"first": {"ID": 1}}}`)
assert.Nil(t, UnmarshalJsonBytes(content, &c)) assert.Nil(t, UnmarshalJsonBytes(content, &c))
assert.Equal(t, 1, len(c.Persons)) assert.Equal(t, 1, len(c.Persons))
assert.Equal(t, 1, c.Persons["first"].Id) assert.Equal(t, 1, c.Persons["first"].ID)
} }
func TestUnmarshalBytesMapEmptyStructSlice(t *testing.T) { func TestUnmarshalBytesMapEmptyStructSlice(t *testing.T) {
var c struct { var c struct {
Persons map[string][]struct { Persons map[string][]struct {
Id int ID int
Name string `json:"name,optional"` Name string `json:"name,optional"`
} }
} }
@@ -555,22 +555,22 @@ func TestUnmarshalBytesMapEmptyStructSlice(t *testing.T) {
func TestUnmarshalBytesMapStructSlice(t *testing.T) { func TestUnmarshalBytesMapStructSlice(t *testing.T) {
var c struct { var c struct {
Persons map[string][]struct { Persons map[string][]struct {
Id int ID int
Name string `json:"name,optional"` Name string `json:"name,optional"`
} }
} }
content := []byte(`{"Persons": {"first": [{"Id": 1, "name": "kevin"}]}}`) content := []byte(`{"Persons": {"first": [{"ID": 1, "name": "kevin"}]}}`)
assert.Nil(t, UnmarshalJsonBytes(content, &c)) assert.Nil(t, UnmarshalJsonBytes(content, &c))
assert.Equal(t, 1, len(c.Persons)) assert.Equal(t, 1, len(c.Persons))
assert.Equal(t, 1, c.Persons["first"][0].Id) assert.Equal(t, 1, c.Persons["first"][0].ID)
assert.Equal(t, "kevin", c.Persons["first"][0].Name) assert.Equal(t, "kevin", c.Persons["first"][0].Name)
} }
func TestUnmarshalBytesMapEmptyStructPtrSlice(t *testing.T) { func TestUnmarshalBytesMapEmptyStructPtrSlice(t *testing.T) {
var c struct { var c struct {
Persons map[string][]*struct { Persons map[string][]*struct {
Id int ID int
Name string `json:"name,optional"` Name string `json:"name,optional"`
} }
} }
@@ -584,26 +584,26 @@ func TestUnmarshalBytesMapEmptyStructPtrSlice(t *testing.T) {
func TestUnmarshalBytesMapStructPtrSlice(t *testing.T) { func TestUnmarshalBytesMapStructPtrSlice(t *testing.T) {
var c struct { var c struct {
Persons map[string][]*struct { Persons map[string][]*struct {
Id int ID int
Name string `json:"name,optional"` Name string `json:"name,optional"`
} }
} }
content := []byte(`{"Persons": {"first": [{"Id": 1, "name": "kevin"}]}}`) content := []byte(`{"Persons": {"first": [{"ID": 1, "name": "kevin"}]}}`)
assert.Nil(t, UnmarshalJsonBytes(content, &c)) assert.Nil(t, UnmarshalJsonBytes(content, &c))
assert.Equal(t, 1, len(c.Persons)) assert.Equal(t, 1, len(c.Persons))
assert.Equal(t, 1, c.Persons["first"][0].Id) assert.Equal(t, 1, c.Persons["first"][0].ID)
assert.Equal(t, "kevin", c.Persons["first"][0].Name) assert.Equal(t, "kevin", c.Persons["first"][0].Name)
} }
func TestUnmarshalBytesMapStructPtrSliceMissingPartial(t *testing.T) { func TestUnmarshalBytesMapStructPtrSliceMissingPartial(t *testing.T) {
var c struct { var c struct {
Persons map[string][]*struct { Persons map[string][]*struct {
Id int ID int
Name string Name string
} }
} }
content := []byte(`{"Persons": {"first": [{"Id": 1}]}}`) content := []byte(`{"Persons": {"first": [{"ID": 1}]}}`)
assert.NotNil(t, UnmarshalJsonBytes(content, &c)) assert.NotNil(t, UnmarshalJsonBytes(content, &c))
} }
@@ -611,15 +611,15 @@ func TestUnmarshalBytesMapStructPtrSliceMissingPartial(t *testing.T) {
func TestUnmarshalBytesMapStructPtrSliceOptional(t *testing.T) { func TestUnmarshalBytesMapStructPtrSliceOptional(t *testing.T) {
var c struct { var c struct {
Persons map[string][]*struct { Persons map[string][]*struct {
Id int ID int
Name string `json:"name,optional"` Name string `json:"name,optional"`
} }
} }
content := []byte(`{"Persons": {"first": [{"Id": 1}]}}`) content := []byte(`{"Persons": {"first": [{"ID": 1}]}}`)
assert.Nil(t, UnmarshalJsonBytes(content, &c)) assert.Nil(t, UnmarshalJsonBytes(content, &c))
assert.Equal(t, 1, len(c.Persons)) assert.Equal(t, 1, len(c.Persons))
assert.Equal(t, 1, c.Persons["first"][0].Id) assert.Equal(t, 1, c.Persons["first"][0].ID)
} }
func TestUnmarshalStructOptional(t *testing.T) { func TestUnmarshalStructOptional(t *testing.T) {

Some files were not shown because too many files have changed in this diff Show More