retry.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. // Copyright 2016 The etcd Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package clientv3
  15. import (
  16. "context"
  17. "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
  18. pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
  19. "google.golang.org/grpc"
  20. "google.golang.org/grpc/codes"
  21. "google.golang.org/grpc/status"
  22. )
  23. type retryPolicy uint8
  24. const (
  25. repeatable retryPolicy = iota
  26. nonRepeatable
  27. )
  28. type rpcFunc func(ctx context.Context) error
  29. type retryRPCFunc func(context.Context, rpcFunc, retryPolicy) error
  30. type retryStopErrFunc func(error) bool
  31. // immutable requests (e.g. Get) should be retried unless it's
  32. // an obvious server-side error (e.g. rpctypes.ErrRequestTooLarge).
  33. //
  34. // "isRepeatableStopError" returns "true" when an immutable request
  35. // is interrupted by server-side or gRPC-side error and its status
  36. // code is not transient (!= codes.Unavailable).
  37. //
  38. // Returning "true" means retry should stop, since client cannot
  39. // handle itself even with retries.
  40. func isRepeatableStopError(err error) bool {
  41. eErr := rpctypes.Error(err)
  42. // always stop retry on etcd errors
  43. if serverErr, ok := eErr.(rpctypes.EtcdError); ok && serverErr.Code() != codes.Unavailable {
  44. return true
  45. }
  46. // only retry if unavailable
  47. ev, ok := status.FromError(err)
  48. if !ok {
  49. return false
  50. }
  51. return ev.Code() != codes.Unavailable
  52. }
  53. // mutable requests (e.g. Put, Delete, Txn) should only be retried
  54. // when the status code is codes.Unavailable when initial connection
  55. // has not been established (no pinned endpoint).
  56. //
  57. // "isNonRepeatableStopError" returns "true" when a mutable request
  58. // is interrupted by non-transient error that client cannot handle itself,
  59. // or transient error while the connection has already been established
  60. // (pinned endpoint exists).
  61. //
  62. // Returning "true" means retry should stop, otherwise it violates
  63. // write-at-most-once semantics.
  64. func isNonRepeatableStopError(err error) bool {
  65. if ev, ok := status.FromError(err); ok && ev.Code() != codes.Unavailable {
  66. return true
  67. }
  68. desc := rpctypes.ErrorDesc(err)
  69. return desc != "there is no address available" && desc != "there is no connection available"
  70. }
  71. // TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface?
  72. /*
  73. func (c *Client) newRetryWrapper() retryRPCFunc {
  74. return func(rpcCtx context.Context, f rpcFunc, rp retryPolicy) error {
  75. var isStop retryStopErrFunc
  76. switch rp {
  77. case repeatable:
  78. isStop = isRepeatableStopError
  79. case nonRepeatable:
  80. isStop = isNonRepeatableStopError
  81. }
  82. for {
  83. if err := readyWait(rpcCtx, c.ctx, c.balancer.ConnectNotify()); err != nil {
  84. return err
  85. }
  86. pinned := c.balancer.Pinned()
  87. err := f(rpcCtx)
  88. if err == nil {
  89. return nil
  90. }
  91. lg.Lvl(4).Infof("clientv3/retry: error %q on pinned endpoint %q", err.Error(), pinned)
  92. if s, ok := status.FromError(err); ok && (s.Code() == codes.Unavailable || s.Code() == codes.DeadlineExceeded || s.Code() == codes.Internal) {
  93. // mark this before endpoint switch is triggered
  94. c.balancer.HostPortError(pinned, err)
  95. c.balancer.Next()
  96. lg.Lvl(4).Infof("clientv3/retry: switching from %q due to error %q", pinned, err.Error())
  97. }
  98. if isStop(err) {
  99. return err
  100. }
  101. }
  102. }
  103. }*/
  104. /*
  105. func (c *Client) newAuthRetryWrapper(retryf retryRPCFunc) retryRPCFunc {
  106. return func(rpcCtx context.Context, f rpcFunc, rp retryPolicy) error {
  107. for {
  108. pinned := c.balancer.Pinned()
  109. err := retryf(rpcCtx, f, rp)
  110. if err == nil {
  111. return nil
  112. }
  113. lg.Lvl(4).Infof("clientv3/auth-retry: error %q on pinned endpoint %q", err.Error(), pinned)
  114. // always stop retry on etcd errors other than invalid auth token
  115. if rpctypes.Error(err) == rpctypes.ErrInvalidAuthToken {
  116. gterr := c.getToken(rpcCtx)
  117. if gterr != nil {
  118. lg.Lvl(4).Infof("clientv3/auth-retry: cannot retry due to error %q(%q) on pinned endpoint %q", err.Error(), gterr.Error(), pinned)
  119. return err // return the original error for simplicity
  120. }
  121. continue
  122. }
  123. return err
  124. }
  125. }
  126. }*/
  127. type retryKVClient struct {
  128. kc pb.KVClient
  129. retryf retryRPCFunc
  130. }
  131. // RetryKVClient implements a KVClient.
  132. func RetryKVClient(c *Client) pb.KVClient {
  133. return pb.NewKVClient(c.conn)
  134. // TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface?
  135. /*return &retryKVClient{
  136. kc: pb.NewKVClient(c.conn),
  137. retryf: c.newAuthRetryWrapper(c.newRetryWrapper()),
  138. }*/
  139. }
  140. func (rkv *retryKVClient) Range(ctx context.Context, in *pb.RangeRequest, opts ...grpc.CallOption) (resp *pb.RangeResponse, err error) {
  141. err = rkv.retryf(ctx, func(rctx context.Context) error {
  142. resp, err = rkv.kc.Range(rctx, in, opts...)
  143. return err
  144. }, repeatable)
  145. return resp, err
  146. }
  147. func (rkv *retryKVClient) Put(ctx context.Context, in *pb.PutRequest, opts ...grpc.CallOption) (resp *pb.PutResponse, err error) {
  148. err = rkv.retryf(ctx, func(rctx context.Context) error {
  149. resp, err = rkv.kc.Put(rctx, in, opts...)
  150. return err
  151. }, nonRepeatable)
  152. return resp, err
  153. }
  154. func (rkv *retryKVClient) DeleteRange(ctx context.Context, in *pb.DeleteRangeRequest, opts ...grpc.CallOption) (resp *pb.DeleteRangeResponse, err error) {
  155. err = rkv.retryf(ctx, func(rctx context.Context) error {
  156. resp, err = rkv.kc.DeleteRange(rctx, in, opts...)
  157. return err
  158. }, nonRepeatable)
  159. return resp, err
  160. }
  161. func (rkv *retryKVClient) Txn(ctx context.Context, in *pb.TxnRequest, opts ...grpc.CallOption) (resp *pb.TxnResponse, err error) {
  162. // TODO: "repeatable" for read-only txn
  163. err = rkv.retryf(ctx, func(rctx context.Context) error {
  164. resp, err = rkv.kc.Txn(rctx, in, opts...)
  165. return err
  166. }, nonRepeatable)
  167. return resp, err
  168. }
  169. func (rkv *retryKVClient) Compact(ctx context.Context, in *pb.CompactionRequest, opts ...grpc.CallOption) (resp *pb.CompactionResponse, err error) {
  170. err = rkv.retryf(ctx, func(rctx context.Context) error {
  171. resp, err = rkv.kc.Compact(rctx, in, opts...)
  172. return err
  173. }, nonRepeatable)
  174. return resp, err
  175. }
  176. type retryLeaseClient struct {
  177. lc pb.LeaseClient
  178. retryf retryRPCFunc
  179. }
  180. // RetryLeaseClient implements a LeaseClient.
  181. func RetryLeaseClient(c *Client) pb.LeaseClient {
  182. return pb.NewLeaseClient(c.conn)
  183. // TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface?
  184. /*return &retryLeaseClient{
  185. lc: pb.NewLeaseClient(c.conn),
  186. retryf: c.newAuthRetryWrapper(c.newRetryWrapper()),
  187. }*/
  188. }
  189. func (rlc *retryLeaseClient) LeaseTimeToLive(ctx context.Context, in *pb.LeaseTimeToLiveRequest, opts ...grpc.CallOption) (resp *pb.LeaseTimeToLiveResponse, err error) {
  190. err = rlc.retryf(ctx, func(rctx context.Context) error {
  191. resp, err = rlc.lc.LeaseTimeToLive(rctx, in, opts...)
  192. return err
  193. }, repeatable)
  194. return resp, err
  195. }
  196. func (rlc *retryLeaseClient) LeaseLeases(ctx context.Context, in *pb.LeaseLeasesRequest, opts ...grpc.CallOption) (resp *pb.LeaseLeasesResponse, err error) {
  197. err = rlc.retryf(ctx, func(rctx context.Context) error {
  198. resp, err = rlc.lc.LeaseLeases(rctx, in, opts...)
  199. return err
  200. }, repeatable)
  201. return resp, err
  202. }
  203. func (rlc *retryLeaseClient) LeaseGrant(ctx context.Context, in *pb.LeaseGrantRequest, opts ...grpc.CallOption) (resp *pb.LeaseGrantResponse, err error) {
  204. err = rlc.retryf(ctx, func(rctx context.Context) error {
  205. resp, err = rlc.lc.LeaseGrant(rctx, in, opts...)
  206. return err
  207. }, repeatable)
  208. return resp, err
  209. }
  210. func (rlc *retryLeaseClient) LeaseRevoke(ctx context.Context, in *pb.LeaseRevokeRequest, opts ...grpc.CallOption) (resp *pb.LeaseRevokeResponse, err error) {
  211. err = rlc.retryf(ctx, func(rctx context.Context) error {
  212. resp, err = rlc.lc.LeaseRevoke(rctx, in, opts...)
  213. return err
  214. }, repeatable)
  215. return resp, err
  216. }
  217. func (rlc *retryLeaseClient) LeaseKeepAlive(ctx context.Context, opts ...grpc.CallOption) (stream pb.Lease_LeaseKeepAliveClient, err error) {
  218. err = rlc.retryf(ctx, func(rctx context.Context) error {
  219. stream, err = rlc.lc.LeaseKeepAlive(rctx, opts...)
  220. return err
  221. }, repeatable)
  222. return stream, err
  223. }
  224. type retryClusterClient struct {
  225. cc pb.ClusterClient
  226. retryf retryRPCFunc
  227. }
  228. // RetryClusterClient implements a ClusterClient.
  229. func RetryClusterClient(c *Client) pb.ClusterClient {
  230. return pb.NewClusterClient(c.conn)
  231. // TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface?
  232. /*return &retryClusterClient{
  233. cc: pb.NewClusterClient(c.conn),
  234. retryf: c.newRetryWrapper(),
  235. }*/
  236. }
  237. func (rcc *retryClusterClient) MemberList(ctx context.Context, in *pb.MemberListRequest, opts ...grpc.CallOption) (resp *pb.MemberListResponse, err error) {
  238. err = rcc.retryf(ctx, func(rctx context.Context) error {
  239. resp, err = rcc.cc.MemberList(rctx, in, opts...)
  240. return err
  241. }, repeatable)
  242. return resp, err
  243. }
  244. func (rcc *retryClusterClient) MemberAdd(ctx context.Context, in *pb.MemberAddRequest, opts ...grpc.CallOption) (resp *pb.MemberAddResponse, err error) {
  245. err = rcc.retryf(ctx, func(rctx context.Context) error {
  246. resp, err = rcc.cc.MemberAdd(rctx, in, opts...)
  247. return err
  248. }, nonRepeatable)
  249. return resp, err
  250. }
  251. func (rcc *retryClusterClient) MemberRemove(ctx context.Context, in *pb.MemberRemoveRequest, opts ...grpc.CallOption) (resp *pb.MemberRemoveResponse, err error) {
  252. err = rcc.retryf(ctx, func(rctx context.Context) error {
  253. resp, err = rcc.cc.MemberRemove(rctx, in, opts...)
  254. return err
  255. }, nonRepeatable)
  256. return resp, err
  257. }
  258. func (rcc *retryClusterClient) MemberUpdate(ctx context.Context, in *pb.MemberUpdateRequest, opts ...grpc.CallOption) (resp *pb.MemberUpdateResponse, err error) {
  259. err = rcc.retryf(ctx, func(rctx context.Context) error {
  260. resp, err = rcc.cc.MemberUpdate(rctx, in, opts...)
  261. return err
  262. }, nonRepeatable)
  263. return resp, err
  264. }
  265. type retryMaintenanceClient struct {
  266. mc pb.MaintenanceClient
  267. retryf retryRPCFunc
  268. }
  269. // RetryMaintenanceClient implements a Maintenance.
  270. func RetryMaintenanceClient(c *Client, conn *grpc.ClientConn) pb.MaintenanceClient {
  271. return pb.NewMaintenanceClient(conn)
  272. // TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface?
  273. /*return &retryMaintenanceClient{
  274. mc: pb.NewMaintenanceClient(conn),
  275. retryf: c.newRetryWrapper(),
  276. }*/
  277. }
  278. func (rmc *retryMaintenanceClient) Alarm(ctx context.Context, in *pb.AlarmRequest, opts ...grpc.CallOption) (resp *pb.AlarmResponse, err error) {
  279. err = rmc.retryf(ctx, func(rctx context.Context) error {
  280. resp, err = rmc.mc.Alarm(rctx, in, opts...)
  281. return err
  282. }, repeatable)
  283. return resp, err
  284. }
  285. func (rmc *retryMaintenanceClient) Status(ctx context.Context, in *pb.StatusRequest, opts ...grpc.CallOption) (resp *pb.StatusResponse, err error) {
  286. err = rmc.retryf(ctx, func(rctx context.Context) error {
  287. resp, err = rmc.mc.Status(rctx, in, opts...)
  288. return err
  289. }, repeatable)
  290. return resp, err
  291. }
  292. func (rmc *retryMaintenanceClient) Hash(ctx context.Context, in *pb.HashRequest, opts ...grpc.CallOption) (resp *pb.HashResponse, err error) {
  293. err = rmc.retryf(ctx, func(rctx context.Context) error {
  294. resp, err = rmc.mc.Hash(rctx, in, opts...)
  295. return err
  296. }, repeatable)
  297. return resp, err
  298. }
  299. func (rmc *retryMaintenanceClient) HashKV(ctx context.Context, in *pb.HashKVRequest, opts ...grpc.CallOption) (resp *pb.HashKVResponse, err error) {
  300. err = rmc.retryf(ctx, func(rctx context.Context) error {
  301. resp, err = rmc.mc.HashKV(rctx, in, opts...)
  302. return err
  303. }, repeatable)
  304. return resp, err
  305. }
  306. func (rmc *retryMaintenanceClient) Snapshot(ctx context.Context, in *pb.SnapshotRequest, opts ...grpc.CallOption) (stream pb.Maintenance_SnapshotClient, err error) {
  307. err = rmc.retryf(ctx, func(rctx context.Context) error {
  308. stream, err = rmc.mc.Snapshot(rctx, in, opts...)
  309. return err
  310. }, repeatable)
  311. return stream, err
  312. }
  313. func (rmc *retryMaintenanceClient) MoveLeader(ctx context.Context, in *pb.MoveLeaderRequest, opts ...grpc.CallOption) (resp *pb.MoveLeaderResponse, err error) {
  314. err = rmc.retryf(ctx, func(rctx context.Context) error {
  315. resp, err = rmc.mc.MoveLeader(rctx, in, opts...)
  316. return err
  317. }, repeatable)
  318. return resp, err
  319. }
  320. func (rmc *retryMaintenanceClient) Defragment(ctx context.Context, in *pb.DefragmentRequest, opts ...grpc.CallOption) (resp *pb.DefragmentResponse, err error) {
  321. err = rmc.retryf(ctx, func(rctx context.Context) error {
  322. resp, err = rmc.mc.Defragment(rctx, in, opts...)
  323. return err
  324. }, nonRepeatable)
  325. return resp, err
  326. }
  327. type retryAuthClient struct {
  328. ac pb.AuthClient
  329. retryf retryRPCFunc
  330. }
  331. // RetryAuthClient implements a AuthClient.
  332. func RetryAuthClient(c *Client) pb.AuthClient {
  333. return pb.NewAuthClient(c.conn)
  334. // TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface?
  335. /*return &retryAuthClient{
  336. ac: pb.NewAuthClient(c.conn),
  337. retryf: c.newRetryWrapper(),
  338. }*/
  339. }
  340. func (rac *retryAuthClient) UserList(ctx context.Context, in *pb.AuthUserListRequest, opts ...grpc.CallOption) (resp *pb.AuthUserListResponse, err error) {
  341. err = rac.retryf(ctx, func(rctx context.Context) error {
  342. resp, err = rac.ac.UserList(rctx, in, opts...)
  343. return err
  344. }, repeatable)
  345. return resp, err
  346. }
  347. func (rac *retryAuthClient) UserGet(ctx context.Context, in *pb.AuthUserGetRequest, opts ...grpc.CallOption) (resp *pb.AuthUserGetResponse, err error) {
  348. err = rac.retryf(ctx, func(rctx context.Context) error {
  349. resp, err = rac.ac.UserGet(rctx, in, opts...)
  350. return err
  351. }, repeatable)
  352. return resp, err
  353. }
  354. func (rac *retryAuthClient) RoleGet(ctx context.Context, in *pb.AuthRoleGetRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleGetResponse, err error) {
  355. err = rac.retryf(ctx, func(rctx context.Context) error {
  356. resp, err = rac.ac.RoleGet(rctx, in, opts...)
  357. return err
  358. }, repeatable)
  359. return resp, err
  360. }
  361. func (rac *retryAuthClient) RoleList(ctx context.Context, in *pb.AuthRoleListRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleListResponse, err error) {
  362. err = rac.retryf(ctx, func(rctx context.Context) error {
  363. resp, err = rac.ac.RoleList(rctx, in, opts...)
  364. return err
  365. }, repeatable)
  366. return resp, err
  367. }
  368. func (rac *retryAuthClient) AuthEnable(ctx context.Context, in *pb.AuthEnableRequest, opts ...grpc.CallOption) (resp *pb.AuthEnableResponse, err error) {
  369. err = rac.retryf(ctx, func(rctx context.Context) error {
  370. resp, err = rac.ac.AuthEnable(rctx, in, opts...)
  371. return err
  372. }, nonRepeatable)
  373. return resp, err
  374. }
  375. func (rac *retryAuthClient) AuthDisable(ctx context.Context, in *pb.AuthDisableRequest, opts ...grpc.CallOption) (resp *pb.AuthDisableResponse, err error) {
  376. err = rac.retryf(ctx, func(rctx context.Context) error {
  377. resp, err = rac.ac.AuthDisable(rctx, in, opts...)
  378. return err
  379. }, nonRepeatable)
  380. return resp, err
  381. }
  382. func (rac *retryAuthClient) UserAdd(ctx context.Context, in *pb.AuthUserAddRequest, opts ...grpc.CallOption) (resp *pb.AuthUserAddResponse, err error) {
  383. err = rac.retryf(ctx, func(rctx context.Context) error {
  384. resp, err = rac.ac.UserAdd(rctx, in, opts...)
  385. return err
  386. }, nonRepeatable)
  387. return resp, err
  388. }
  389. func (rac *retryAuthClient) UserDelete(ctx context.Context, in *pb.AuthUserDeleteRequest, opts ...grpc.CallOption) (resp *pb.AuthUserDeleteResponse, err error) {
  390. err = rac.retryf(ctx, func(rctx context.Context) error {
  391. resp, err = rac.ac.UserDelete(rctx, in, opts...)
  392. return err
  393. }, nonRepeatable)
  394. return resp, err
  395. }
  396. func (rac *retryAuthClient) UserChangePassword(ctx context.Context, in *pb.AuthUserChangePasswordRequest, opts ...grpc.CallOption) (resp *pb.AuthUserChangePasswordResponse, err error) {
  397. err = rac.retryf(ctx, func(rctx context.Context) error {
  398. resp, err = rac.ac.UserChangePassword(rctx, in, opts...)
  399. return err
  400. }, nonRepeatable)
  401. return resp, err
  402. }
  403. func (rac *retryAuthClient) UserGrantRole(ctx context.Context, in *pb.AuthUserGrantRoleRequest, opts ...grpc.CallOption) (resp *pb.AuthUserGrantRoleResponse, err error) {
  404. err = rac.retryf(ctx, func(rctx context.Context) error {
  405. resp, err = rac.ac.UserGrantRole(rctx, in, opts...)
  406. return err
  407. }, nonRepeatable)
  408. return resp, err
  409. }
  410. func (rac *retryAuthClient) UserRevokeRole(ctx context.Context, in *pb.AuthUserRevokeRoleRequest, opts ...grpc.CallOption) (resp *pb.AuthUserRevokeRoleResponse, err error) {
  411. err = rac.retryf(ctx, func(rctx context.Context) error {
  412. resp, err = rac.ac.UserRevokeRole(rctx, in, opts...)
  413. return err
  414. }, nonRepeatable)
  415. return resp, err
  416. }
  417. func (rac *retryAuthClient) RoleAdd(ctx context.Context, in *pb.AuthRoleAddRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleAddResponse, err error) {
  418. err = rac.retryf(ctx, func(rctx context.Context) error {
  419. resp, err = rac.ac.RoleAdd(rctx, in, opts...)
  420. return err
  421. }, nonRepeatable)
  422. return resp, err
  423. }
  424. func (rac *retryAuthClient) RoleDelete(ctx context.Context, in *pb.AuthRoleDeleteRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleDeleteResponse, err error) {
  425. err = rac.retryf(ctx, func(rctx context.Context) error {
  426. resp, err = rac.ac.RoleDelete(rctx, in, opts...)
  427. return err
  428. }, nonRepeatable)
  429. return resp, err
  430. }
  431. func (rac *retryAuthClient) RoleGrantPermission(ctx context.Context, in *pb.AuthRoleGrantPermissionRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleGrantPermissionResponse, err error) {
  432. err = rac.retryf(ctx, func(rctx context.Context) error {
  433. resp, err = rac.ac.RoleGrantPermission(rctx, in, opts...)
  434. return err
  435. }, nonRepeatable)
  436. return resp, err
  437. }
  438. func (rac *retryAuthClient) RoleRevokePermission(ctx context.Context, in *pb.AuthRoleRevokePermissionRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleRevokePermissionResponse, err error) {
  439. err = rac.retryf(ctx, func(rctx context.Context) error {
  440. resp, err = rac.ac.RoleRevokePermission(rctx, in, opts...)
  441. return err
  442. }, nonRepeatable)
  443. return resp, err
  444. }
  445. func (rac *retryAuthClient) Authenticate(ctx context.Context, in *pb.AuthenticateRequest, opts ...grpc.CallOption) (resp *pb.AuthenticateResponse, err error) {
  446. err = rac.retryf(ctx, func(rctx context.Context) error {
  447. resp, err = rac.ac.Authenticate(rctx, in, opts...)
  448. return err
  449. }, nonRepeatable)
  450. return resp, err
  451. }