Repository: gopcua/opcua
Pull request #542 (2022-01-07 12:36:40)

Add context to client methods
See #528 

Needs to be merged after #541 



Patch:
diff --git a/client.go b/client.go
index dc18cef..58013e1 100644
--- a/client.go
+++ b/client.go
@@ -35,7 +35,7 @@ func GetEndpoints(ctx context.Context, endpoint string, opts ...Option) ([]*ua.E
 		return nil, err
 	}
 	defer c.Close()
-	res, err := c.GetEndpoints()
+	res, err := c.GetEndpointsWithContext(ctx)
 	if err != nil {
 		return nil, err
 	}
@@ -193,7 +193,7 @@ func (c *Client) Connect(ctx context.Context) (err error) {
 		return err
 	}
 
-	s, err := c.CreateSession(c.cfg.session)
+	s, err := c.CreateSessionWithContext(ctx, c.cfg.session)
 	if err != nil {
 		c.Close()
 		stats.RecordError(err)
@@ -201,7 +201,7 @@ func (c *Client) Connect(ctx context.Context) (err error) {
 		return err
 	}
 
-	if err := c.ActivateSession(s); err != nil {
+	if err := c.ActivateSessionWithContext(ctx, s); err != nil {
 		c.Close()
 		stats.RecordError(err)
 
@@ -381,7 +381,7 @@ func (c *Client) monitor(ctx context.Context) {
 						}
 
 						dlog.Printf("trying to restore session")
-						if err := c.ActivateSession(s); err != nil {
+						if err := c.ActivateSessionWithContext(ctx, s); err != nil {
 							dlog.Printf("restore session failed")
 							action = recreateSession
 							continue
@@ -395,13 +395,13 @@ func (c *Client) monitor(ctx context.Context) {
 						// create a new session to replace the previous one
 
 						dlog.Printf("trying to recreate session")
-						s, err := c.CreateSession(c.cfg.session)
+						s, err := c.CreateSessionWithContext(ctx, c.cfg.session)
 						if err != nil {
 							dlog.Printf("recreate session failed: %v", err)
 							action = createSecureChannel
 							continue
 						}
-						if err := c.ActivateSession(s); err != nil {
+						if err := c.ActivateSessionWithContext(ctx, s); err != nil {
 							dlog.Printf("reactivate session failed: %v", err)
 							action = createSecureChannel
 							continue
@@ -458,7 +458,7 @@ func (c *Client) monitor(ctx context.Context) {
 						// populated in the previous step.
 
 						for _, id := range subsToRepublish {
-							if err := c.republishSubscription(id, availableSeqs[id]); err != nil {
+							if err := c.republishSubscription(ctx, id, availableSeqs[id]); err != nil {
 								dlog.Printf("republish of subscription %d failed", id)
 								subsToRecreate = append(subsToRecreate, id)
 							}
@@ -644,6 +644,9 @@ type Session struct {
 //
 // See Part 4, 5.6.2
 func (c *Client) CreateSession(cfg *uasc.SessionConfig) (*Session, error) {
+	return c.CreateSessionWithContext(context.Background(), cfg)
+}
+func (c *Client) CreateSessionWithContext(ctx context.Context, cfg *uasc.SessionConfig) (*Session, error) {
 	if c.SecureChannel() == nil {
 		return nil, ua.StatusBadServerNotConnected
 	}
@@ -670,7 +673,7 @@ func (c *Client) CreateSession(cfg *uasc.SessionConfig) (*Session, error) {
 	var s *Session
 	// for the CreateSessionRequest the authToken is always nil.
 	// use c.SecureChannel().SendRequest() to enforce this.
-	err := c.SecureChannel().SendRequest(req, nil, func(v interface{}) error {
+	err := c.SecureChannel().SendRequestWithContext(ctx, req, nil, func(v interface{}) error {
 		var res *ua.CreateSessionResponse
 		if err := safeAssign(v, &res); err != nil {
 			return err
@@ -727,7 +730,11 @@ func anonymousPolicyID(endpoints []*ua.EndpointDescription) string {
 // session call DetachSession.
 //
 // See Part 4, 5.6.3
+
 func (c *Client) ActivateSession(s *Session) error {
+	return c.ActivateSessionWithContext(context.Background(), s)
+}
+func (c *Client) ActivateSessionWithContext(ctx context.Context, s *Session) error {
 	if c.SecureChannel() == nil {
 		return ua.StatusBadServerNotConnected
 	}
@@ -776,7 +783,7 @@ func (c *Client) ActivateSession(s *Session) error {
 		UserIdentityToken:          ua.NewExtensionObject(s.cfg.UserIdentityToken),
 		UserTokenSignature:         s.cfg.UserTokenSignature,
 	}
-	return c.SecureChannel().SendRequest(req, s.resp.AuthenticationToken, func(v interface{}) error {
+	return c.SecureChannel().SendRequestWithContext(ctx, req, s.resp.AuthenticationToken, func(v interface{}) error {
 		var res *ua.ActivateSessionResponse
 		if err := safeAssign(v, &res); err != nil {
 			return err
@@ -803,8 +810,11 @@ func (c *Client) ActivateSession(s *Session) error {
 //
 // See Part 4, 5.6.4
 func (c *Client) CloseSession() error {
+	return c.CloseSessionWithContext(context.Background())
+}
+func (c *Client) CloseSessionWithContext(ctx context.Context) error {
 	stats.Client().Add("CloseSession", 1)
-	if err := c.closeSession(c.Session()); err != nil {
+	if err := c.closeSession(ctx, c.Session()); err != nil {
 		return err
 	}
 	c.setSession(nil)
@@ -812,13 +822,13 @@ func (c *Client) CloseSession() error {
 }
 
 // closeSession closes the given session.
-func (c *Client) closeSession(s *Session) error {
+func (c *Client) closeSession(ctx context.Context, s *Session) error {
 	if s == nil {
 		return nil
 	}
 	req := &ua.CloseSessionRequest{DeleteSubscriptions: true}
 	var res *ua.CloseSessionResponse
-	return c.Send(req, func(v interface{}) error {
+	return c.SendWithContext(ctx, req, func(v interface{}) error {
 		return safeAssign(v, &res)
 	})
 }
@@ -826,7 +836,15 @@ func (c *Client) closeSession(s *Session) error {
 // DetachSession removes the session from the client without closing it. The
 // caller is responsible to close or re-activate the session. If the client
 // does not have an active session the function returns no error.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (c *Client) DetachSession() (*Session, error) {
+	return c.DetachSessionWithContext(context.Background())
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (c *Client) DetachSessionWithContext(ctx context.Context) (*Session, error) {
 	stats.Client().Add("DetachSession", 1)
 	s := c.Session()
 	c.setSession(nil)
@@ -836,10 +854,18 @@ func (c *Client) DetachSession() (*Session, error) {
 // Send sends the request via the secure channel and registers a handler for
 // the response. If the client has an active session it injects the
 // authentication token.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (c *Client) Send(req ua.Request, h func(interface{}) error) error {
+	return c.SendWithContext(context.Background(), req, h)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (c *Client) SendWithContext(ctx context.Context, req ua.Request, h func(interface{}) error) error {
 	stats.Client().Add("Send", 1)
 
-	err := c.sendWithTimeout(req, c.cfg.sechan.RequestTimeout, h)
+	err := c.sendWithTimeout(ctx, req, c.cfg.sechan.RequestTimeout, h)
 	stats.RecordError(err)
 
 	return err
@@ -848,7 +874,7 @@ func (c *Client) Send(req ua.Request, h func(interface{}) error) error {
 // sendWithTimeout sends the request via the secure channel with a custom timeout and registers a handler for
 // the response. If the client has an active session it injects the
 // authentication token.
-func (c *Client) sendWithTimeout(req ua.Request, timeout time.Duration, h func(interface{}) error) error {
+func (c *Client) sendWithTimeout(ctx context.Context, req ua.Request, timeout time.Duration, h func(interface{}) error) error {
 	if c.SecureChannel() == nil {
 		return ua.StatusBadServerNotConnected
 	}
@@ -856,7 +882,7 @@ func (c *Client) sendWithTimeout(req ua.Request, timeout time.Duration, h func(i
 	if s := c.Session(); s != nil {
 		authToken = s.resp.AuthenticationToken
 	}
-	return c.SecureChannel().SendRequestWithTimeout(req, authToken, timeout, h)
+	return c.SecureChannel().SendRequestWithTimeoutWithContext(ctx, req, authToken, timeout, h)
 }
 
 // Node returns a node object which accesses its attributes
@@ -865,14 +891,23 @@ func (c *Client) Node(id *ua.NodeID) *Node {
 	return &Node{ID: id, c: c}
 }
 
+// GetEndpoints returns the list of available endpoints of the server.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (c *Client) GetEndpoints() (*ua.GetEndpointsResponse, error) {
+	return c.GetEndpointsWithContext(context.Background())
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (c *Client) GetEndpointsWithContext(ctx context.Context) (*ua.GetEndpointsResponse, error) {
 	stats.Client().Add("GetEndpoints", 1)
 
 	req := &ua.GetEndpointsRequest{
 		EndpointURL: c.endpointURL,
 	}
 	var res *ua.GetEndpointsResponse
-	err := c.Send(req, func(v interface{}) error {
+	err := c.SendWithContext(ctx, req, func(v interface{}) error {
 		return safeAssign(v, &res)
 	})
 	return res, err
@@ -902,7 +937,15 @@ func cloneReadRequest(req *ua.ReadRequest) *ua.ReadRequest {
 //
 // By default, the function requests the value of the nodes
 // in the default encoding of the server.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (c *Client) Read(req *ua.ReadRequest) (*ua.ReadResponse, error) {
+	return c.ReadWithContext(context.Background(), req)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (c *Client) ReadWithContext(ctx context.Context, req *ua.ReadRequest) (*ua.ReadResponse, error) {
 	stats.Client().Add("Read", 1)
 	stats.Client().Add("NodesToRead", int64(len(req.NodesToRead)))
 
@@ -911,7 +954,7 @@ func (c *Client) Read(req *ua.ReadRequest) (*ua.ReadResponse, error) {
 	req = cloneReadRequest(req)
 
 	var res *ua.ReadResponse
-	err := c.Send(req, func(v interface{}) error {
+	err := c.SendWithContext(ctx, req, func(v interface{}) error {
 		err := safeAssign(v, &res)
 
 		// If the client cannot decode an extension object then its
@@ -937,12 +980,20 @@ func (c *Client) Read(req *ua.ReadRequest) (*ua.ReadResponse, error) {
 }
 
 // Write executes a synchronous write request.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (c *Client) Write(req *ua.WriteRequest) (*ua.WriteResponse, error) {
+	return c.WriteWithContext(context.Background(), req)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (c *Client) WriteWithContext(ctx context.Context, req *ua.WriteRequest) (*ua.WriteResponse, error) {
 	stats.Client().Add("Write", 1)
 	stats.Client().Add("NodesToWrite", int64(len(req.NodesToWrite)))
 
 	var res *ua.WriteResponse
-	err := c.Send(req, func(v interface{}) error {
+	err := c.SendWithContext(ctx, req, func(v interface{}) error {
 		return safeAssign(v, &res)
 	})
 	return res, err
@@ -973,7 +1024,15 @@ func cloneBrowseRequest(req *ua.BrowseRequest) *ua.BrowseRequest {
 }
 
 // Browse executes a synchronous browse request.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (c *Client) Browse(req *ua.BrowseRequest) (*ua.BrowseResponse, error) {
+	return c.BrowseWithContext(context.Background(), req)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (c *Client) BrowseWithContext(ctx context.Context, req *ua.BrowseRequest) (*ua.BrowseResponse, error) {
 	stats.Client().Add("Browse", 1)
 	stats.Client().Add("NodesToBrowse", int64(len(req.NodesToBrowse)))
 
@@ -982,21 +1041,29 @@ func (c *Client) Browse(req *ua.BrowseRequest) (*ua.BrowseResponse, error) {
 	req = cloneBrowseRequest(req)
 
 	var res *ua.BrowseResponse
-	err := c.Send(req, func(v interface{}) error {
+	err := c.SendWithContext(ctx, req, func(v interface{}) error {
 		return safeAssign(v, &res)
 	})
 	return res, err
 }
 
 // Call executes a synchronous call request for a single method.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (c *Client) Call(req *ua.CallMethodRequest) (*ua.CallMethodResult, error) {
+	return c.CallWithContext(context.Background(), req)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (c *Client) CallWithContext(ctx context.Context, req *ua.CallMethodRequest) (*ua.CallMethodResult, error) {
 	stats.Client().Add("Call", 1)
 
 	creq := &ua.CallRequest{
 		MethodsToCall: []*ua.CallMethodRequest{req},
 	}
 	var res *ua.CallResponse
-	err := c.Send(creq, func(v interface{}) error {
+	err := c.SendWithContext(ctx, creq, func(v interface{}) error {
 		return safeAssign(v, &res)
 	})
 	if err != nil {
@@ -1009,43 +1076,76 @@ func (c *Client) Call(req *ua.CallMethodRequest) (*ua.CallMethodResult, error) {
 }
 
 // BrowseNext executes a synchronous browse request.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (c *Client) BrowseNext(req *ua.BrowseNextRequest) (*ua.BrowseNextResponse, error) {
+	return c.BrowseNextWithContext(context.Background(), req)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (c *Client) BrowseNextWithContext(ctx context.Context, req *ua.BrowseNextRequest) (*ua.BrowseNextResponse, error) {
 	stats.Client().Add("BrowseNext", 1)
 
 	var res *ua.BrowseNextResponse
-	err := c.Send(req, func(v interface{}) error {
+	err := c.SendWithContext(ctx, req, func(v interface{}) error {
 		return safeAssign(v, &res)
 	})
 	return res, err
 }
 
 // RegisterNodes registers node ids for more efficient reads.
+//
 // Part 4, Section 5.8.5
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (c *Client) RegisterNodes(req *ua.RegisterNodesRequest) (*ua.RegisterNodesResponse, error) {
+	return c.RegisterNodesWithContext(context.Background(), req)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (c *Client) RegisterNodesWithContext(ctx context.Context, req *ua.RegisterNodesRequest) (*ua.RegisterNodesResponse, error) {
 	stats.Client().Add("RegisterNodes", 1)
 	stats.Client().Add("NodesToRegister", int64(len(req.NodesToRegister)))
 
 	var res *ua.RegisterNodesResponse
-	err := c.Send(req, func(v interface{}) error {
+	err := c.SendWithContext(ctx, req, func(v interface{}) error {
 		return safeAssign(v, &res)
 	})
 	return res, err
 }
 
 // UnregisterNodes unregisters node ids previously registered with RegisterNodes.
+//
 // Part 4, Section 5.8.6
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (c *Client) UnregisterNodes(req *ua.UnregisterNodesRequest) (*ua.UnregisterNodesResponse, error) {
+	return c.UnregisterNodesWithContext(context.Background(), req)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (c *Client) UnregisterNodesWithContext(ctx context.Context, req *ua.UnregisterNodesRequest) (*ua.UnregisterNodesResponse, error) {
 	stats.Client().Add("UnregisterNodes", 1)
 	stats.Client().Add("NodesToUnregister", int64(len(req.NodesToUnregister)))
 
 	var res *ua.UnregisterNodesResponse
-	err := c.Send(req, func(v interface{}) error {
+	err := c.SendWithContext(ctx, req, func(v interface{}) error {
 		return safeAssign(v, &res)
 	})
 	return res, err
 }
 
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (c *Client) HistoryReadRawModified(nodes []*ua.HistoryReadValueID, details *ua.ReadRawModifiedDetails) (*ua.HistoryReadResponse, error) {
+	return c.HistoryReadRawModifiedWithContext(context.Background(), nodes, details)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (c *Client) HistoryReadRawModifiedWithContext(ctx context.Context, nodes []*ua.HistoryReadValueID, details *ua.ReadRawModifiedDetails) (*ua.HistoryReadResponse, error) {
 	stats.Client().Add("HistoryReadRawModified", 1)
 	stats.Client().Add("HistoryReadValueID", int64(len(nodes)))
 
@@ -1062,17 +1162,25 @@ func (c *Client) HistoryReadRawModified(nodes []*ua.HistoryReadValueID, details
 	}
 
 	var res *ua.HistoryReadResponse
-	err := c.Send(req, func(v interface{}) error {
+	err := c.SendWithContext(ctx, req, func(v interface{}) error {
 		return safeAssign(v, &res)
 	})
 	return res, err
 }
 
 // NamespaceArray returns the list of namespaces registered on the server.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (c *Client) NamespaceArray() ([]string, error) {
+	return c.NamespaceArrayWithContext(context.Background())
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (c *Client) NamespaceArrayWithContext(ctx context.Context) ([]string, error) {
 	stats.Client().Add("NamespaceArray", 1)
 	node := c.Node(ua.NewNumericNodeID(0, id.Server_NamespaceArray))
-	v, err := node.Value()
+	v, err := node.ValueWithContext(ctx)
 	if err != nil {
 		return nil, err
 	}
@@ -1085,9 +1193,17 @@ func (c *Client) NamespaceArray() ([]string, error) {
 }
 
 // FindNamespace returns the id of the namespace with the given name.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (c *Client) FindNamespace(name string) (uint16, error) {
+	return c.FindNamespaceWithContext(context.Background(), name)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (c *Client) FindNamespaceWithContext(ctx context.Context, name string) (uint16, error) {
 	stats.Client().Add("FindNamespace", 1)
-	nsa, err := c.NamespaceArray()
+	nsa, err := c.NamespaceArrayWithContext(ctx)
 	if err != nil {
 		return 0, err
 	}
@@ -1100,9 +1216,17 @@ func (c *Client) FindNamespace(name string) (uint16, error) {
 }
 
 // UpdateNamespaces updates the list of cached namespaces from the server.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (c *Client) UpdateNamespaces() error {
+	return c.UpdateNamespacesWithContext(context.Background())
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (c *Client) UpdateNamespacesWithContext(ctx context.Context) error {
 	stats.Client().Add("UpdateNamespaces", 1)
-	ns, err := c.NamespaceArray()
+	ns, err := c.NamespaceArrayWithContext(ctx)
 	if err != nil {
 		return err
 	}
diff --git a/client_sub.go b/client_sub.go
index 48efd21..7bd3c52 100644
--- a/client_sub.go
+++ b/client_sub.go
@@ -113,7 +113,7 @@ func (c *Client) transferSubscriptions(ids []uint32) (*ua.TransferSubscriptionsR
 }
 
 // republishSubscriptions sends republish requests for the given subscription id.
-func (c *Client) republishSubscription(id uint32, availableSeq []uint32) error {
+func (c *Client) republishSubscription(ctx context.Context, id uint32, availableSeq []uint32) error {
 	c.subMux.RLock()
 	defer c.subMux.RUnlock()
 
@@ -123,7 +123,7 @@ func (c *Client) republishSubscription(id uint32, availableSeq []uint32) error {
 	}
 
 	debug.Printf("republishing subscription %d", sub.SubscriptionID)
-	if err := c.sendRepublishRequests(sub, availableSeq); err != nil {
+	if err := c.sendRepublishRequests(ctx, sub, availableSeq); err != nil {
 		status, ok := err.(ua.StatusCode)
 		if !ok {
 			return err
@@ -144,7 +144,7 @@ func (c *Client) republishSubscription(id uint32, availableSeq []uint32) error {
 // sendRepublishRequests sends republish requests for the given subscription
 // until it gets a BadMessageNotAvailable which implies that there are no
 // more messages to restore.
-func (c *Client) sendRepublishRequests(sub *Subscription, availableSeq []uint32) error {
+func (c *Client) sendRepublishRequests(ctx context.Context, sub *Subscription, availableSeq []uint32) error {
 	// todo(fs): check if sub.nextSeq is in the available sequence numbers
 	// todo(fs): if not then we need to decide whether we fail b/c of data loss
 	// todo(fs): or whether we log it and continue.
@@ -170,7 +170,7 @@ func (c *Client) sendRepublishRequests(sub *Subscription, availableSeq []uint32)
 
 		debug.Printf("RepublishRequest: req=%s", debug.ToJSON(req))
 		var res *ua.RepublishResponse
-		err := c.SecureChannel().SendRequest(req, c.Session().resp.AuthenticationToken, func(v interface{}) error {
+		err := c.SecureChannel().SendRequestWithContext(ctx, req, c.Session().resp.AuthenticationToken, func(v interface{}) error {
 			return safeAssign(v, &res)
 		})
 		debug.Printf("RepublishResponse: res=%s err=%v", debug.ToJSON(res), err)
@@ -379,7 +379,7 @@ func (c *Client) publish(ctx context.Context) error {
 
 	// send the next publish request
 	// note that res contains data even if an error was returned
-	res, err := c.sendPublishRequest()
+	res, err := c.sendPublishRequest(ctx)
 	stats.RecordError(err)
 	switch {
 	case err == io.EOF:
@@ -508,7 +508,7 @@ func (c *Client) handleNotification(ctx context.Context, sub *Subscription, res
 	})
 }
 
-func (c *Client) sendPublishRequest() (*ua.PublishResponse, error) {
+func (c *Client) sendPublishRequest(ctx context.Context) (*ua.PublishResponse, error) {
 	dlog := debug.NewPrefixLogger("publish: ")
 
 	c.subMux.RLock()
@@ -522,7 +522,7 @@ func (c *Client) sendPublishRequest() (*ua.PublishResponse, error) {
 
 	dlog.Printf("PublishRequest: %s", debug.ToJSON(req))
 	var res *ua.PublishResponse
-	err := c.sendWithTimeout(req, c.publishTimeout(), func(v interface{}) error {
+	err := c.sendWithTimeout(ctx, req, c.publishTimeout(), func(v interface{}) error {
 		return safeAssign(v, &res)
 	})
 	stats.RecordError(err)
diff --git a/examples/accesslevel/accesslevel.go b/examples/accesslevel/accesslevel.go
index 6ae51ef..d28a7d6 100644
--- a/examples/accesslevel/accesslevel.go
+++ b/examples/accesslevel/accesslevel.go
@@ -29,7 +29,7 @@ func main() {
 	if err := c.Connect(ctx); err != nil {
 		log.Fatal(err)
 	}
-	defer c.Close()
+	defer c.CloseSessionWithContext(ctx)
 
 	id, err := ua.ParseNodeID(*nodeID)
 	if err != nil {
@@ -37,19 +37,19 @@ func main() {
 	}
 
 	n := c.Node(id)
-	accessLevel, err := n.AccessLevel()
+	accessLevel, err := n.AccessLevelWithContext(ctx)
 	if err != nil {
 		log.Fatal(err)
 	}
 	log.Print("AccessLevel: ", accessLevel)
 
-	userAccessLevel, err := n.UserAccessLevel()
+	userAccessLevel, err := n.UserAccessLevelWithContext(ctx)
 	if err != nil {
 		log.Fatal(err)
 	}
 	log.Print("UserAccessLevel: ", userAccessLevel)
 
-	v, err := n.Value()
+	v, err := n.ValueWithContext(ctx)
 	switch {
 	case err != nil:
 		log.Fatal(err)
diff --git a/examples/browse/browse.go b/examples/browse/browse.go
index 6310693..3e564a4 100644
--- a/examples/browse/browse.go
+++ b/examples/browse/browse.go
@@ -45,13 +45,13 @@ func join(a, b string) string {
 	return a + "." + b
 }
 
-func browse(n *opcua.Node, path string, level int) ([]NodeDef, error) {
+func browse(ctx context.Context, n *opcua.Node, path string, level int) ([]NodeDef, error) {
 	// fmt.Printf("node:%s path:%q level:%d\n", n, path, level)
 	if level > 10 {
 		return nil, nil
 	}
 
-	attrs, err := n.Attributes(ua.AttributeIDNodeClass, ua.AttributeIDBrowseName, ua.AttributeIDDescription, ua.AttributeIDAccessLevel, ua.AttributeIDDataType)
+	attrs, err := n.AttributesWithContext(ctx, ua.AttributeIDNodeClass, ua.AttributeIDBrowseName, ua.AttributeIDDescription, ua.AttributeIDAccessLevel, ua.AttributeIDDataType)
 	if err != nil {
 		return nil, err
 	}
@@ -138,13 +138,13 @@ func browse(n *opcua.Node, path string, level int) ([]NodeDef, error) {
 	}
 
 	browseChildren := func(refType uint32) error {
-		refs, err := n.ReferencedNodes(refType, ua.BrowseDirectionForward, ua.NodeClassAll, true)
+		refs, err := n.ReferencedNodesWithContext(ctx, refType, ua.BrowseDirectionForward, ua.NodeClassAll, true)
 		if err != nil {
 			return errors.Errorf("References: %d: %s", refType, err)
 		}
 		// fmt.Printf("found %d child refs\n", len(refs))
 		for _, rn := range refs {
-			children, err := browse(rn, def.Path, level+1)
+			children, err := browse(ctx, rn, def.Path, level+1)
 			if err != nil {
 				return errors.Errorf("browse children: %s", err)
 			}
@@ -178,14 +178,14 @@ func main() {
 	if err := c.Connect(ctx); err != nil {
 		log.Fatal(err)
 	}
-	defer c.Close()
+	defer c.CloseSessionWithContext(ctx)
 
 	id, err := ua.ParseNodeID(*nodeID)
 	if err != nil {
 		log.Fatalf("invalid node id: %s", err)
 	}
 
-	nodeList, err := browse(c.Node(id), "", 0)
+	nodeList, err := browse(ctx, c.Node(id), "", 0)
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/examples/crypto/crypto.go b/examples/crypto/crypto.go
index 0592269..ef80c30 100644
--- a/examples/crypto/crypto.go
+++ b/examples/crypto/crypto.go
@@ -65,7 +65,7 @@ func main() {
 	if err := c.Connect(ctx); err != nil {
 		log.Fatal(err)
 	}
-	defer c.Close()
+	defer c.CloseSessionWithContext(ctx)
 
 	// Use our connection (read the server's time)
 	v, err := c.Node(ua.NewNumericNodeID(0, 2258)).Value()
diff --git a/examples/datetime/datetime.go b/examples/datetime/datetime.go
index bde0aa1..88a865d 100644
--- a/examples/datetime/datetime.go
+++ b/examples/datetime/datetime.go
@@ -51,9 +51,9 @@ func main() {
 	if err := c.Connect(ctx); err != nil {
 		log.Fatal(err)
 	}
-	defer c.Close()
+	defer c.CloseSessionWithContext(ctx)
 
-	v, err := c.Node(ua.NewNumericNodeID(0, 2258)).Value()
+	v, err := c.Node(ua.NewNumericNodeID(0, 2258)).ValueWithContext(ctx)
 	switch {
 	case err != nil:
 		log.Fatal(err)
diff --git a/examples/history-read/history-read.go b/examples/history-read/history-read.go
index cb2a234..2fed4a2 100644
--- a/examples/history-read/history-read.go
+++ b/examples/history-read/history-read.go
@@ -28,7 +28,7 @@ func main() {
 	if err := c.Connect(ctx); err != nil {
 		log.Fatal(err)
 	}
-	defer c.Close()
+	defer c.CloseSessionWithContext(ctx)
 
 	id, err := ua.ParseNodeID(*nodeID)
 	if err != nil {
@@ -37,7 +37,7 @@ func main() {
 
 	// HistoryRead with ContinuationPoint use
 	nodesToRequest := []*ua.HistoryReadValueID{
-		&ua.HistoryReadValueID{
+		{
 			NodeID:       id,
 			DataEncoding: &ua.QualifiedName{},
 		},
@@ -54,7 +54,7 @@ func main() {
 		// Reset old nodes
 		nodesToRequest = make([]*ua.HistoryReadValueID, 0)
 
-		data, err := c.HistoryReadRawModified(nodes, &ua.ReadRawModifiedDetails{
+		data, err := c.HistoryReadRawModifiedWithContext(ctx, nodes, &ua.ReadRawModifiedDetails{
 			IsReadModified: false,
 			StartTime:      time.Now().UTC().AddDate(0, -1, 0),
 			EndTime:        time.Now().UTC().AddDate(0, 1, 0),
diff --git a/examples/method/method.go b/examples/method/method.go
index 1922408..b1cf17d 100644
--- a/examples/method/method.go
+++ b/examples/method/method.go
@@ -28,7 +28,7 @@ func main() {
 	if err := c.Connect(ctx); err != nil {
 		log.Fatal(err)
 	}
-	defer c.Close()
+	defer c.CloseSessionWithContext(ctx)
 
 	in := int64(12)
 	req := &ua.CallMethodRequest{
@@ -37,7 +37,7 @@ func main() {
 		InputArguments: []*ua.Variant{ua.MustVariant(in)},
 	}
 
-	resp, err := c.Call(req)
+	resp, err := c.CallWithContext(ctx, req)
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/examples/monitor/monitor.go b/examples/monitor/monitor.go
index 8663a0b..4e4118a 100644
--- a/examples/monitor/monitor.go
+++ b/examples/monitor/monitor.go
@@ -68,7 +68,7 @@ func main() {
 		log.Fatal(err)
 	}
 
-	defer c.Close()
+	defer c.CloseSessionWithContext(ctx)
 
 	m, err := monitor.NewNodeMonitor(c)
 	if err != nil {
diff --git a/examples/read/read.go b/examples/read/read.go
index ec50d9d..2e5beac 100644
--- a/examples/read/read.go
+++ b/examples/read/read.go
@@ -29,7 +29,7 @@ func main() {
 	if err := c.Connect(ctx); err != nil {
 		log.Fatal(err)
 	}
-	defer c.Close()
+	defer c.CloseSessionWithContext(ctx)
 
 	id, err := ua.ParseNodeID(*nodeID)
 	if err != nil {
@@ -44,7 +44,7 @@ func main() {
 		TimestampsToReturn: ua.TimestampsToReturnBoth,
 	}
 
-	resp, err := c.Read(req)
+	resp, err := c.ReadWithContext(ctx, req)
 	if err != nil {
 		log.Fatalf("Read failed: %s", err)
 	}
diff --git a/examples/regread/regread.go b/examples/regread/regread.go
index 942f77a..db5d1da 100644
--- a/examples/regread/regread.go
+++ b/examples/regread/regread.go
@@ -29,14 +29,14 @@ func main() {
 	if err := c.Connect(ctx); err != nil {
 		log.Fatal(err)
 	}
-	defer c.Close()
+	defer c.CloseSessionWithContext(ctx)
 
 	id, err := ua.ParseNodeID(*nodeID)
 	if err != nil {
 		log.Fatalf("invalid node id: %v", err)
 	}
 
-	regResp, err := c.RegisterNodes(&ua.RegisterNodesRequest{
+	regResp, err := c.RegisterNodesWithContext(ctx, &ua.RegisterNodesRequest{
 		NodesToRegister: []*ua.NodeID{id},
 	})
 	if err != nil {
@@ -46,12 +46,12 @@ func main() {
 	req := &ua.ReadRequest{
 		MaxAge: 2000,
 		NodesToRead: []*ua.ReadValueID{
-			&ua.ReadValueID{NodeID: regResp.RegisteredNodeIDs[0]},
+			{NodeID: regResp.RegisteredNodeIDs[0]},
 		},
 		TimestampsToReturn: ua.TimestampsToReturnBoth,
 	}
 
-	resp, err := c.Read(req)
+	resp, err := c.ReadWithContext(ctx, req)
 	if err != nil {
 		log.Fatalf("Read failed: %s", err)
 	}
@@ -60,7 +60,7 @@ func main() {
 	}
 	log.Print(resp.Results[0].Value.Value())
 
-	_, err = c.UnregisterNodes(&ua.UnregisterNodesRequest{
+	_, err = c.UnregisterNodesWithContext(ctx, &ua.UnregisterNodesRequest{
 		NodesToUnregister: []*ua.NodeID{id},
 	})
 	if err != nil {
diff --git a/examples/subscribe/subscribe.go b/examples/subscribe/subscribe.go
index d9fd6ce..006cd65 100644
--- a/examples/subscribe/subscribe.go
+++ b/examples/subscribe/subscribe.go
@@ -62,7 +62,7 @@ func main() {
 	if err := c.Connect(ctx); err != nil {
 		log.Fatal(err)
 	}
-	defer c.Close()
+	defer c.CloseSessionWithContext(ctx)
 
 	notifyCh := make(chan *opcua.PublishNotificationData)
 
diff --git a/examples/translate/translate.go b/examples/translate/translate.go
index f126002..ad4b669 100644
--- a/examples/translate/translate.go
+++ b/examples/translate/translate.go
@@ -31,10 +31,10 @@ func main() {
 	if err := c.Connect(ctx); err != nil {
 		log.Fatal(err)
 	}
-	defer c.Close()
+	defer c.CloseSessionWithContext(ctx)
 
 	root := c.Node(ua.NewTwoByteNodeID(id.ObjectsFolder))
-	nodeID, err := root.TranslateBrowsePathInNamespaceToNodeID(uint16(*ns), *nodePath)
+	nodeID, err := root.TranslateBrowsePathInNamespaceToNodeIDWithContext(ctx, uint16(*ns), *nodePath)
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/examples/trigger/trigger.go b/examples/trigger/trigger.go
index f1a84ad..2cc3729 100644
--- a/examples/trigger/trigger.go
+++ b/examples/trigger/trigger.go
@@ -63,7 +63,7 @@ func main() {
 	if err := c.Connect(ctx); err != nil {
 		log.Fatal(err)
 	}
-	defer c.Close()
+	defer c.CloseSessionWithContext(ctx)
 
 	notifyCh := make(chan *opcua.PublishNotificationData)
 
diff --git a/examples/udt/udt.go b/examples/udt/udt.go
index cf4b0d3..7cba516 100644
--- a/examples/udt/udt.go
+++ b/examples/udt/udt.go
@@ -59,9 +59,9 @@ func main() {
 	if err := c.Connect(ctx); err != nil {
 		log.Fatal(err)
 	}
-	defer c.Close()
+	defer c.CloseSessionWithContext(ctx)
 
-	v, err := c.Node(id).Value()
+	v, err := c.Node(id).ValueWithContext(ctx)
 	switch {
 	case err != nil:
 		log.Fatal(err)
diff --git a/examples/write/write.go b/examples/write/write.go
index 54d8229..1521a6c 100644
--- a/examples/write/write.go
+++ b/examples/write/write.go
@@ -30,7 +30,7 @@ func main() {
 	if err := c.Connect(ctx); err != nil {
 		log.Fatal(err)
 	}
-	defer c.Close()
+	defer c.CloseSessionWithContext(ctx)
 
 	id, err := ua.ParseNodeID(*nodeID)
 	if err != nil {
@@ -55,7 +55,7 @@ func main() {
 		},
 	}
 
-	resp, err := c.Write(req)
+	resp, err := c.WriteWithContext(ctx, req)
 	if err != nil {
 		log.Fatalf("Read failed: %s", err)
 	}
diff --git a/node.go b/node.go
index d0cdc76..87931a6 100644
--- a/node.go
+++ b/node.go
@@ -5,6 +5,7 @@
 package opcua
 
 import (
+	"context"
 	"strings"
 
 	"github.com/gopcua/opcua/id"
@@ -26,8 +27,16 @@ func (n *Node) String() string {
 }
 
 // NodeClass returns the node class attribute.
-func (n *Node) NodeClass() (ua.NodeClass, error) {
-	v, err := n.Attribute(ua.AttributeIDNodeClass)
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
+func (n *Node) NodeClass(ctx context.Context) (ua.NodeClass, error) {
+	return n.NodeClassWithContext(context.Background())
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (n *Node) NodeClassWithContext(ctx context.Context) (ua.NodeClass, error) {
+	v, err := n.AttributeWithContext(ctx, ua.AttributeIDNodeClass)
 	if err != nil {
 		return 0, err
 	}
@@ -35,8 +44,16 @@ func (n *Node) NodeClass() (ua.NodeClass, error) {
 }
 
 // BrowseName returns the browse name of the node.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (n *Node) BrowseName() (*ua.QualifiedName, error) {
-	v, err := n.Attribute(ua.AttributeIDBrowseName)
+	return n.BrowseNameWithContext(context.Background())
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (n *Node) BrowseNameWithContext(ctx context.Context) (*ua.QualifiedName, error) {
+	v, err := n.AttributeWithContext(ctx, ua.AttributeIDBrowseName)
 	if err != nil {
 		return nil, err
 	}
@@ -44,8 +61,16 @@ func (n *Node) BrowseName() (*ua.QualifiedName, error) {
 }
 
 // Description returns the description of the node.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (n *Node) Description() (*ua.LocalizedText, error) {
-	v, err := n.Attribute(ua.AttributeIDDescription)
+	return n.DescriptionWithContext(context.Background())
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (n *Node) DescriptionWithContext(ctx context.Context) (*ua.LocalizedText, error) {
+	v, err := n.AttributeWithContext(ctx, ua.AttributeIDDescription)
 	if err != nil {
 		return nil, err
 	}
@@ -53,8 +78,16 @@ func (n *Node) Description() (*ua.LocalizedText, error) {
 }
 
 // DisplayName returns the display name of the node.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (n *Node) DisplayName() (*ua.LocalizedText, error) {
-	v, err := n.Attribute(ua.AttributeIDDisplayName)
+	return n.DisplayNameWithContext(context.Background())
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (n *Node) DisplayNameWithContext(ctx context.Context) (*ua.LocalizedText, error) {
+	v, err := n.AttributeWithContext(ctx, ua.AttributeIDDisplayName)
 	if err != nil {
 		return nil, err
 	}
@@ -64,8 +97,16 @@ func (n *Node) DisplayName() (*ua.LocalizedText, error) {
 // AccessLevel returns the access level of the node.
 // The returned value is a mask where multiple values can be
 // set, e.g. read and write.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (n *Node) AccessLevel() (ua.AccessLevelType, error) {
-	v, err := n.Attribute(ua.AttributeIDAccessLevel)
+	return n.AccessLevelWithContext(context.Background())
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (n *Node) AccessLevelWithContext(ctx context.Context) (ua.AccessLevelType, error) {
+	v, err := n.AttributeWithContext(ctx, ua.AttributeIDAccessLevel)
 	if err != nil {
 		return 0, err
 	}
@@ -74,8 +115,16 @@ func (n *Node) AccessLevel() (ua.AccessLevelType, error) {
 
 // HasAccessLevel returns true if all bits from mask are
 // set in the access level mask of the node.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (n *Node) HasAccessLevel(mask ua.AccessLevelType) (bool, error) {
-	v, err := n.AccessLevel()
+	return n.HasAccessLevelWithContext(context.Background(), mask)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (n *Node) HasAccessLevelWithContext(ctx context.Context, mask ua.AccessLevelType) (bool, error) {
+	v, err := n.AccessLevelWithContext(ctx)
 	if err != nil {
 		return false, err
 	}
@@ -83,8 +132,16 @@ func (n *Node) HasAccessLevel(mask ua.AccessLevelType) (bool, error) {
 }
 
 // UserAccessLevel returns the access level of the node.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (n *Node) UserAccessLevel() (ua.AccessLevelType, error) {
-	v, err := n.Attribute(ua.AttributeIDUserAccessLevel)
+	return n.UserAccessLevelWithContext(context.Background())
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (n *Node) UserAccessLevelWithContext(ctx context.Context) (ua.AccessLevelType, error) {
+	v, err := n.AttributeWithContext(ctx, ua.AttributeIDUserAccessLevel)
 	if err != nil {
 		return 0, err
 	}
@@ -93,8 +150,16 @@ func (n *Node) UserAccessLevel() (ua.AccessLevelType, error) {
 
 // HasUserAccessLevel returns true if all bits from mask are
 // set in the user access level mask of the node.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (n *Node) HasUserAccessLevel(mask ua.AccessLevelType) (bool, error) {
-	v, err := n.UserAccessLevel()
+	return n.HasUserAccessLevelWithContext(context.Background(), mask)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (n *Node) HasUserAccessLevelWithContext(ctx context.Context, mask ua.AccessLevelType) (bool, error) {
+	v, err := n.UserAccessLevelWithContext(ctx)
 	if err != nil {
 		return false, err
 	}
@@ -102,15 +167,31 @@ func (n *Node) HasUserAccessLevel(mask ua.AccessLevelType) (bool, error) {
 }
 
 // Value returns the value of the node.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (n *Node) Value() (*ua.Variant, error) {
-	return n.Attribute(ua.AttributeIDValue)
+	return n.ValueWithContext(context.Background())
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (n *Node) ValueWithContext(ctx context.Context) (*ua.Variant, error) {
+	return n.AttributeWithContext(ctx, ua.AttributeIDValue)
 }
 
 // Attribute returns the attribute of the node. with the given id.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (n *Node) Attribute(attrID ua.AttributeID) (*ua.Variant, error) {
+	return n.AttributeWithContext(context.Background(), attrID)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (n *Node) AttributeWithContext(ctx context.Context, attrID ua.AttributeID) (*ua.Variant, error) {
 	rv := &ua.ReadValueID{NodeID: n.ID, AttributeID: attrID}
 	req := &ua.ReadRequest{NodesToRead: []*ua.ReadValueID{rv}}
-	res, err := n.c.Read(req)
+	res, err := n.c.ReadWithContext(ctx, req)
 	if err != nil {
 		return nil, err
 	}
@@ -126,32 +207,59 @@ func (n *Node) Attribute(attrID ua.AttributeID) (*ua.Variant, error) {
 	return value, nil
 }
 
+// Attributes returns the given node attributes.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (n *Node) Attributes(attrID ...ua.AttributeID) ([]*ua.DataValue, error) {
+	return n.AttributesWithContext(context.Background(), attrID...)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (n *Node) AttributesWithContext(ctx context.Context, attrID ...ua.AttributeID) ([]*ua.DataValue, error) {
 	req := &ua.ReadRequest{}
 	for _, id := range attrID {
 		rv := &ua.ReadValueID{NodeID: n.ID, AttributeID: id}
 		req.NodesToRead = append(req.NodesToRead, rv)
 	}
-	res, err := n.c.Read(req)
+	res, err := n.c.ReadWithContext(ctx, req)
 	if err != nil {
 		return nil, err
 	}
 	return res.Results, nil
 }
 
+// Children returns the child nodes which match the node class mask.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (n *Node) Children(refs uint32, mask ua.NodeClass) ([]*Node, error) {
+	return n.ChildrenWithContext(context.Background(), refs, mask)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (n *Node) ChildrenWithContext(ctx context.Context, refs uint32, mask ua.NodeClass) ([]*Node, error) {
 	if refs == 0 {
 		refs = id.HierarchicalReferences
 	}
-	return n.ReferencedNodes(refs, ua.BrowseDirectionForward, mask, true)
+	return n.ReferencedNodesWithContext(ctx, refs, ua.BrowseDirectionForward, mask, true)
 }
 
+// ReferencedNodes returns the nodes referenced by this node.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (n *Node) ReferencedNodes(refs uint32, dir ua.BrowseDirection, mask ua.NodeClass, includeSubtypes bool) ([]*Node, error) {
+	return n.ReferencedNodesWithContext(context.Background(), refs, dir, mask, includeSubtypes)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (n *Node) ReferencedNodesWithContext(ctx context.Context, refs uint32, dir ua.BrowseDirection, mask ua.NodeClass, includeSubtypes bool) ([]*Node, error) {
 	if refs == 0 {
 		refs = id.References
 	}
 	var nodes []*Node
-	res, err := n.References(refs, dir, mask, includeSubtypes)
+	res, err := n.ReferencesWithContext(ctx, refs, dir, mask, includeSubtypes)
 	if err != nil {
 		return nil, err
 	}
@@ -162,9 +270,18 @@ func (n *Node) ReferencedNodes(refs uint32, dir ua.BrowseDirection, mask ua.Node
 }
 
 // References returns all references for the node.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
+//
 // todo(fs): this is not complete since it only returns the
 // todo(fs): top-level reference at this point.
 func (n *Node) References(refType uint32, dir ua.BrowseDirection, mask ua.NodeClass, includeSubtypes bool) ([]*ua.ReferenceDescription, error) {
+	return n.ReferencesWithContext(context.Background(), refType, dir, mask, includeSubtypes)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (n *Node) ReferencesWithContext(ctx context.Context, refType uint32, dir ua.BrowseDirection, mask ua.NodeClass, includeSubtypes bool) ([]*ua.ReferenceDescription, error) {
 	if refType == 0 {
 		refType = id.References
 	}
@@ -189,21 +306,21 @@ func (n *Node) References(refType uint32, dir ua.BrowseDirection, mask ua.NodeCl
 		NodesToBrowse:                 []*ua.BrowseDescription{desc},
 	}
 
-	resp, err := n.c.Browse(req)
+	resp, err := n.c.BrowseWithContext(ctx, req)
 	if err != nil {
 		return nil, err
 	}
-	return n.browseNext(resp.Results)
+	return n.browseNext(ctx, resp.Results)
 }
 
-func (n *Node) browseNext(results []*ua.BrowseResult) ([]*ua.ReferenceDescription, error) {
+func (n *Node) browseNext(ctx context.Context, results []*ua.BrowseResult) ([]*ua.ReferenceDescription, error) {
 	refs := results[0].References
 	for len(results[0].ContinuationPoint) > 0 {
 		req := &ua.BrowseNextRequest{
 			ContinuationPoints:        [][]byte{results[0].ContinuationPoint},
 			ReleaseContinuationPoints: false,
 		}
-		resp, err := n.c.BrowseNext(req)
+		resp, err := n.c.BrowseNextWithContext(ctx, req)
 		if err != nil {
 			return nil, err
 		}
@@ -214,7 +331,15 @@ func (n *Node) browseNext(results []*ua.BrowseResult) ([]*ua.ReferenceDescriptio
 }
 
 // TranslateBrowsePathsToNodeIDs translates an array of browseName segments to NodeIDs.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (n *Node) TranslateBrowsePathsToNodeIDs(pathNames []*ua.QualifiedName) (*ua.NodeID, error) {
+	return n.TranslateBrowsePathsToNodeIDsWithContext(context.Background(), pathNames)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (n *Node) TranslateBrowsePathsToNodeIDsWithContext(ctx context.Context, pathNames []*ua.QualifiedName) (*ua.NodeID, error) {
 	req := ua.TranslateBrowsePathsToNodeIDsRequest{
 		BrowsePaths: []*ua.BrowsePath{
 			{
@@ -236,7 +361,7 @@ func (n *Node) TranslateBrowsePathsToNodeIDs(pathNames []*ua.QualifiedName) (*ua
 	}
 
 	var nodeID *ua.NodeID
-	err := n.c.Send(&req, func(i interface{}) error {
+	err := n.c.SendWithContext(ctx, &req, func(i interface{}) error {
 		if resp, ok := i.(*ua.TranslateBrowsePathsToNodeIDsResponse); ok {
 			if len(resp.Results) == 0 {
 				return ua.StatusBadUnexpectedError
@@ -258,12 +383,20 @@ func (n *Node) TranslateBrowsePathsToNodeIDs(pathNames []*ua.QualifiedName) (*ua
 }
 
 // TranslateBrowsePathInNamespaceToNodeID translates a browseName to a NodeID within the same namespace.
+//
+// Note: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (n *Node) TranslateBrowsePathInNamespaceToNodeID(ns uint16, browsePath string) (*ua.NodeID, error) {
+	return n.TranslateBrowsePathInNamespaceToNodeIDWithContext(context.Background(), ns, browsePath)
+}
+
+// Note: Starting with v0.5 this method is superseded by the non 'WithContext' method.
+func (n *Node) TranslateBrowsePathInNamespaceToNodeIDWithContext(ctx context.Context, ns uint16, browsePath string) (*ua.NodeID, error) {
 	segments := strings.Split(browsePath, ".")
 	var names []*ua.QualifiedName
 	for _, segment := range segments {
 		qn := &ua.QualifiedName{NamespaceIndex: ns, Name: segment}
 		names = append(names, qn)
 	}
-	return n.TranslateBrowsePathsToNodeIDs(names)
+	return n.TranslateBrowsePathsToNodeIDsWithContext(ctx, names)
 }
diff --git a/uasc/secure_channel.go b/uasc/secure_channel.go
index 3bacc11..960a5c3 100644
--- a/uasc/secure_channel.go
+++ b/uasc/secure_channel.go
@@ -722,14 +722,26 @@ func (s *SecureChannel) Renew(ctx context.Context) error {
 }
 
 // SendRequest sends the service request and calls h with the response.
+// Deprecated: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (s *SecureChannel) SendRequest(req ua.Request, authToken *ua.NodeID, h func(interface{}) error) error {
-	return s.SendRequestWithTimeout(req, authToken, s.cfg.RequestTimeout, h)
+	return s.SendRequestWithContext(context.Background(), req, authToken, h)
 }
 
+// Note: This method will be replaced by the non "WithContext()" version
+// of this method.
+func (s *SecureChannel) SendRequestWithContext(ctx context.Context, req ua.Request, authToken *ua.NodeID, h func(interface{}) error) error {
+	return s.SendRequestWithTimeoutWithContext(ctx, req, authToken, s.cfg.RequestTimeout, h)
+}
+
+// Deprecated: Starting with v0.5 this method will require a context
+// and the corresponding XXXWithContext(ctx) method will be removed.
 func (s *SecureChannel) SendRequestWithTimeout(req ua.Request, authToken *ua.NodeID, timeout time.Duration, h func(interface{}) error) error {
 	return s.SendRequestWithTimeoutWithContext(context.Background(), req, authToken, timeout, h)
 }
 
+// Note: This method will be replaced by the non "WithContext()" version
+// of this method.
 func (s *SecureChannel) SendRequestWithTimeoutWithContext(ctx context.Context, req ua.Request, authToken *ua.NodeID, timeout time.Duration, h func(interface{}) error) error {
 	s.reqLocker.waitIfLock()
 	active, err := s.getActiveChannelInstance()
@@ -848,7 +860,7 @@ func (s *SecureChannel) close() error {
 	default:
 	}
 
-	err := s.SendRequest(&ua.CloseSecureChannelRequest{}, nil, nil)
+	err := s.SendRequestWithContext(context.Background(), &ua.CloseSecureChannelRequest{}, nil, nil)
 	if err != nil {
 		return err
 	}


Test patch:
diff --git a/client_test.go b/client_test.go
index 760225d..6a0a1c1 100644
--- a/client_test.go
+++ b/client_test.go
@@ -1,6 +1,7 @@
 package opcua
 
 import (
+	"context"
 	"testing"
 
 	"github.com/gopcua/opcua/id"
@@ -10,7 +11,7 @@ import (
 
 func TestClient_Send_DoesNotPanicWhenDisconnected(t *testing.T) {
 	c := NewClient("opc.tcp://example.com:4840")
-	err := c.Send(&ua.ReadRequest{}, func(i interface{}) error {
+	err := c.SendWithContext(context.Background(), &ua.ReadRequest{}, func(i interface{}) error {
 		return nil
 	})
 	verify.Values(t, "", err, ua.StatusBadServerNotConnected)