I have just moved from V0.13.2 to V0.14.2 and am now getting following error in event processing. The error appears to be in the composer-client code, not mine. Any ideas on resolving? Events are still being posted and processed by my app, so my code still appears to work, but the presence of these error messages is troubling and their volume is overwhelming my console window.
error: [EventHub.js]: on.data - Error unmarshalling transaction= TypeError: Cannot read property 'getSerializer' of null
at events.forEach (<path>Z2B_Master/Chapter12/node_modules/composer-client/lib/businessnetworkconnection.js:483:73)
at Array.forEach (native)
at HLFConnection.connection.on (<path>Z2B_Master/Chapter12/node_modules/composer-client/lib/businessnetworkconnection.js:482:29)
at emitOne (events.js:96:13)
at HLFConnection.emit (events.js:188:7)
at ChainCodeCBE.ccEvent.eventHubs.(anonymous function).registerChaincodeEvent [as onEvent] (<path>Z2B_Master/Chapter12/node_modules/composer-connector-hlfv1/lib/hlfconnection.js:231:22)
at <path>Z2B_Master/Chapter12/node_modules/fabric-client/lib/EventHub.js:810:12
at Set.forEach (native)
at EventHub._processChainCodeOnEvents (<path>Z2B_Master/Chapter12/node_modules/fabric-client/lib/EventHub.js:808:14)
at ClientDuplexStream.<anonymous> (<path>Z2B_Master/Chapter12/node_modules/fabric-client/lib/EventHub.js:311:10)
This error is not present using identical code in V0.13.
All events are emitted via a single function in the sample.js file. A transaction calls the function as in the following example:
/**
* create an order to purchase
* #param {org.acme.Z2BTestNetwork.CreateOrder} purchase - the order to be processed
* #transaction
*/
function CreateOrder(purchase) {
purchase.order.buyer = purchase.buyer;
purchase.order.amount = purchase.amount;
purchase.order.financeCo = purchase.financeCo;
purchase.order.created = new Date().toISOString();
purchase.order.status = JSON.stringify(orderStatus.Created);
return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
.then(function (assetRegistry) {
return assetRegistry.update(purchase.order)
.then (function (_res)
{
z2bEmit('Created', purchase.order);
return (_res);
}).catch(function(error){return(error);});
});
}
Each transaction calls the z2bEmit function with a unique _event string.
function z2bEmit(_event, _order)
{
var method = 'z2bEmit';
var factory = getFactory();
var z2bEvent = factory.newEvent(ns, _event);
z2bEvent.orderID = _order.$identifier;
z2bEvent.buyerID = _order.buyer.$identifier;
emit(z2bEvent);
return
}
_order is a defined asset in the cto file, _event is a defined event in the cto file.
The client side code has a single routine, executed once, to set up monitoring:
/**
* Register for all of the available Z2BEvents
* #param {express.req} req - the inbound request object from the client
* #param {express.res} res - the outbound response object for communicating back to client
* #param {express.next} next - an express service to enable post processing prior to responding to the client
*/
exports.init_z2bEvents = function (req, res, next)
{
var method = 'init_z2bEvents';
if (bRegistered) {res.send('Already Registered');}
else{
bRegistered = true;
let _conn = svc.createAlertSocket();
let businessNetworkConnection;
businessNetworkConnection = new BusinessNetworkConnection();
// following line added to deal with eventListener error message that more eventListeners needed to be added
businessNetworkConnection.setMaxListeners(50);
return businessNetworkConnection.connect(config.composer.connectionProfile, config.composer.network, config.composer.adminID, config.composer.adminPW)
.then(() => {
businessNetworkConnection.on('event', (event) => {_monitor(svc.al_connection, svc.f_connection, event); });
res.send('event registration complete');
}).catch((error) => {
console.log(method+' business network connection failed'+error.message);
res.send(method+' business network connection failed'+error.message);
});
}
}
The connectionProfile is 'hlfv1'
and a single monitor routine, which figures out what kind of event has been posted and then uses a web socket to send that info to a browser so that an alert icon can be posted or updated. A shortened version of that function follows. _conn _f_conn continue to work correctly. The _event information is being passed in and continues to parse correctly. The eventhub.js messages appear on every alert, irrespective of how long the program runs.
/**
* _monitor
* #param {web.socket} _conn - web socket connection for general alerts
* #param {web.socket} _f_conn - web socket for finance alerts
* #param {org.acme.z2bNetwork.Event} _event - the event just emitted
*
*/
function _monitor(_conn, _f_conn, _event)
{
var method = '_monitor';
console.log(method+ ' _event received: '+_event.$type+' for Order: '+_event.orderID);
var event = {};
event.type = _event.$type;
event.orderID = _event.orderID;
event.ID = _event.buyerID;
_conn.sendUTF(JSON.stringify(event));
switch (_event.$type)
{
case 'Created':
break;
case 'Bought':
case 'PaymentRequested':
event.ID = _event.sellerID;
_conn.sendUTF(JSON.stringify(event));
event.ID = _event.financeCoID;
_f_conn.sendUTF(JSON.stringify(event));
break;
case 'Ordered':
case 'Cancelled':
case 'Backordered':
event.ID = _event.sellerID;
_conn.sendUTF(JSON.stringify(event));
event.ID = _event.providerID;
_conn.sendUTF(JSON.stringify(event));
break;
default:
break;
}
}
While unable to determine the root cause of this problem, it has gone away with the release of (and upgrade to) hyperledger-composer V0.15.2.
Related
I have different publishers publish to a PubSub Topic. Each message has a specific key. I would like to create subscribers that only pick up the latest message for each specific key within a defined interval. In other words, I would like to have some kind of debounce implemented for my subscribers.
Example (with debounce 2 seconds)
-(x)-(y)-(x)-------(z)-(z)---(x)-----------------> [Topic with messages]
|-------|---------------|execute for x [Subscriber]
2 seconds
|---------------|execute for y [Subscriber]
2 seconds
|---|---------------|execute for z [Subscriber]
2 seconds
|---------------|execute for x [Subscriber]
2 seconds
Ordered Execution Summary:
execute for message with key: y
execute for message with key: x
execute for message with key: z
execute for message with key: x
Implementation
// index.ts
import * as pubsub from '#google-cloud/pubsub';
import * as functions from 'firebase-functions';
import AbortController from 'node-abort-controller';
exports.Debouncer = functions
.runWith({
// runtimeOptions
})
.region('REGION')
.pubsub.topic('TOPIC_NAME')
.onPublish(async (message, context) => {
const key = message.json.key;
// when an equivalent topic is being received, cancel this calculation:
const aborter = await abortHelper<any>(
'TOPIC_NAME',
(message) => message?.key === key
).catch((error) => {
console.error('Failed to init abort helper', error);
throw new Error('Failed to init abort helper');
});
await new Promise((resolve) => setTimeout(resolve, 2000));
// here, run the EXECUTION for the key, unless an abortsignal from the abortHelper was received:
// if(aborter.abortController.signal) ...
aborter.teardown();
/**
* Subscribe to the first subscription found for the specified topic. Once a
* message gets received that is matching `messageMatcher`, the returned
* AbortController reflects the abortet state. Calling the returned teardown
* will cancel the subscription.
*/
async function abortHelper<TMessage>(
topicName: string,
messageMatcher: (message: TMessage) => boolean = () => true
) {
const abortController = new AbortController();
const pubSubClient = new pubsub.PubSub();
const topic = pubSubClient.topic(topicName);
const subscription = await topic
.getSubscriptions()
.then((subscriptionsResponse) => {
// TODO use better approach to find or provide subscription
const subscription = subscriptionsResponse?.[0]?.[0];
if (!subscription) {
throw new Error('no found subscription');
}
return subscription;
});
const listener = (message: TMessage) => {
const matching = messageMatcher(message);
if (matching) {
abortController.abort();
unsubscribeFromPubSubTopicSubscription();
}
};
subscription.addListener('message', listener);
return {
teardown: () => {
unsubscribeFromPubSubTopicSubscription();
},
abortController,
};
function unsubscribeFromPubSubTopicSubscription() {
subscription.removeListener('message', listener);
}
}
});
The initial idea was to register a cloud function to the topic. This cloud function itself then subscribes to the topic as well and waits for the defined interval. If it picks up a message with the same key during the interval, it exits the cloud function. Otherwise, it runs the execution.
Running inside the firebase-emulator this worked fine. However, on production random and hard to debug issues occurred most likely due to parallel execution of the functions.
What would be the best approach to implement such a system in a scalable way? (It does not necessarily have to be with PubSub.)
I set up a simple Backendless API Service and am running it through CodeRunner. As a test, I'm simply getting a record from the database and returning it. I've tried every combination of return type definition in the class annotations that I can think of, and I've assured that the correct record exists and is being returned to the service, but I've never successfully had the record returned using the console, or via a SDK invocation. In every case, the body returned to the invocation is null. My current test uses "Object" as the return type for the getSchedule call - are database objects not objects?
Here is the entire service:
'use strict';
const { DateTime } = require("luxon");
const util = require("util");
class Scheduling {
/**
*
* #param {String} day
* #returns {Object}
*/
getSchedule( day ) {
let t = DateTime.fromISO(day).toMillis();
let q = Backendless.DataQueryBuilder.create().setWhereClause(`day = ${t}`);
Backendless.Data.of("schedules").find(q)
.then(rec => {
console.log(util.inspect(rec,3))
if (rec.length === 1) {
return rec[0]
}
else {
return {error: 404, msg: 'not found'}
}
})
}
}
Backendless.ServerCode.addService( Scheduling )
The "inspect" call indicates I am retrieving the correct record. No errors, the return status of the invocation is always 200. Obviously, I'm missing something about API service return types, please point me in the correct direction.
The problem is the response for the find method is returned after the invocation of getSchedule is complete (because the API invocation is asynchronous).
How about declaring the getSchedule with async and then await for the API invocation?
'use strict';
const { DateTime } = require("luxon");
const util = require("util");
class Scheduling {
/**
*
* #param {String} day
* #returns {Object}
*/
async getSchedule( day ) {
let t = DateTime.fromISO(day).toMillis();
let q = Backendless.DataQueryBuilder.create().setWhereClause(`day = ${t}`);
var rec = await Backendless.Data.of("schedules").find(q);
console.log(util.inspect(rec,3))
if (rec.length === 1) {
return rec[0]
}
else {
return {error: 404, msg: 'not found'}
}
}
}
Backendless.ServerCode.addService( Scheduling )
I'm trying to emit an event when other transaction has been called. But I can't execute my intentions.
I have the following piece of code:
event TransactionAssetEvent {
o BlastAsset eventAsset
o String eventCalledFromTransaction
}
In the logic.js file I have a function that works fine:
async function Transfer(transfer) {
//Some logic with a asset object
TransactionAssetEvent(asset, 'Transfer');
return updateAsset(asset);
}
/**
* Emit a notification that a transaction has occurred
* #param {Object} asset
* #param {String} eventCalledFromTransaction
* #transaction
*/
async function TransactionAssetEvent(asset, eventCalledFromTransaction) {
const factory = getFactory();
let event = factory.newEvent(org.test', 'TransactionAssetEvent');
event.eventAsset = asset;
event.eventCalledFromTransaction = eventCalledFromTransaction;
emit(event);
}
But I have the following error:
Error: t: Transaction processing function TransactionAssetEvent must have 1 function argument of type transaction.
How can emit an event successfully?
I'm implementing a great flow with the events? Or I'm using in a bad way the events?
I look for other post's but I can't implemented the commented flow
let event = factory.newEvent(org.test', 'TransactionAssetEvent');
should be
let event = factory.newEvent('namespace*', 'TransactionAssetEvent');
namespace is basically the namespace of the file where event TransactionAssetEvent is saved
so for example your line will be
let event = factory.newEvent('org.test.eventModelFile', 'TransactionAssetEvent');
Also you missed a ' in factory.newEvent(org.test', 'TransactionAssetEvent');
it should be factory.newEvent('org.test', 'TransactionAssetEvent');
I'm running a relatively simple AWS Function to add a subscription to Stripe.
It runs fine unless I hit it shortly after I just hit it. Just trying to run it in PostMan one after the other fails and returns:
{"errorMessage": "Process exited before completing request"}
The requests are delivered via API Gateway.
Function is configured with 30s timeout and is take ~1300ms to run on the base 128M RAM (issue reproducible # 256M).
I thought this was exactly what Lambda was designed to avoid... I'm second guessing my decision to use Lambda for a (synchronous) mission critical component.
EDIT: As requested, here's the function code:
var stripe = require('stripe');
exports.handler = function (event, context, callback) {
var self = this;
stripe = stripe(getKey(event.stage, 'STRIPE_SECRET_KEY'));
self.createSubscription = createSubscription;
self.validPayload = validPayload;
console.log('event: ', event);
if (self.validPayload(event, context)) {
self.createSubscription(event, stripe, callback, context);
}
/**
* checks that the necessary payload has been received
* if YES: returns true and allows process to continue
* if NO: throws context.fail with useful error message(s)
* operating under custom error code naming convention of
* http code + 3 digit ULM error code
* #param event - from Lambda
* #param context - from Lambda
* #returns {boolean} - whether the payload contains the required data
*/
function validPayload (event, context) {
var errorResponse = {
status: 400,
errors: []
};
if (!event.billing_email) {
errorResponse.errors.push({
code: 400001,
message: "No billing email provided."
})
}
if (!event.plan) {
errorResponse.errors.push({
code: 400002,
message: "No plan was selected."
})
}
if (!event.token) {
errorResponse.errors.push({
code: 400003,
message: "A valid credit card was not provided."
})
}
if (!!errorResponse.errors.length) {
context.fail(JSON.stringify(errorResponse));
return false;
} else {
return true;
}
}
/**
* Creates a new customer & subscription using stripe package method
* if success, executes callback with response data
* if fail, throws context.fail with useful error message(s)
* #param event - from Lambda
* #param stripe - probably not necessary...
* #param callback - from Lambda
* #param context - probably not necessary...
*/
function createSubscription (event, stripe, callback, context) {
stripe.customers.create({
source: event.token,
plan: event.plan,
email: event.billing_email
}, function (err, customer) {
if (err) {
var errorResponse = {
status: 400,
errors: []
};
errorResponse.errors.push({
code: 400004,
message: err.message
});
console.error('Customer/Plan Creation Failed');
callback(JSON.stringify(errorResponse));
} else {
callback(null, {
status: 200,
customer: customer
});
}
});
}
function getKey (stage, keyId) {
var keys = {
STRIPE_SECRET_KEY: {
staging: 'sk_test_123456',
prod: 'sk_live_123456'
}
};
if (stage === 'prod') {
return keys[keyId][stage];
} else {
return keys[keyId]['staging'];
}
}
};
EDIT 2: Dug into CloudWatch and found this error log: TypeError: stripe is not a function at exports.handler (/var/task/exports.js:5:14)
#rowanu is correct, your problem is on this line stripe = stripe(getKey(event.stage, 'STRIPE_SECRET_KEY'));. Since the Lambda stays hot to handle subsequent requests any variables declared outside of the handler function will be seen by each new request that comes in. This should be a simple fix, don't redefine the stripe variable. Something like this would do the trick:
var stripe = require('stripe');
var stripeInstance; // undefined on startup
exports.handler = function (event, context, callback) {
// now define stripeInstance if its not already defined
if(!stripeInstance) {
stripeInstance = stripe(getKey(event.stage, 'STRIPE_SECRET_KEY'));
}
// now all functions will be able to use the same instance of stripe.
// This assumes the event.stage is always the same, if you need a new instance for every request then remove the if statement
// rename all references of stripe to stripeInstance
...
"Process exited before completing request" indicates that your function exited without calling the callback. It is not related to timeouts or throttling.
Usually this indicates that an exception is thrown from a code path that doesn't have adequate error handling.
You will simply need to handle or fix "stripe is not a function at exports.handler (/var/task/exports.js:5:14)" and call the appropriate callback.
I couldn't figure out why protractor timed out when it reach to the code with ignoreSynchronization set to false;
this.countSubscribers = function () {
this.subscriberCount().then(function (count) {
totalSubscribers = count;
});
};
the method works correctly and totalSubscribers variable is getting correct value when
ignoreSynchronization = true;
Code below is a sample of my spec page:
describe("subscriber page test", function () {
"use strict";
var selectedCount = 10;
var subscriberCount;
describe("This test script selects no. of subscribers to be displayed on subscriber page", function () {
/**
* Step-1#Select number of items to be displayed
* Step-2#Get the count of subscribers displayed
* Step-3#check whether the number of subscribers displayed is equal to 10
*/
it("should select the no. of items to displayed and get the count", function () {
browser.ignoreSynchronization = false;
subscriber_page.selectItems(selectedCount);
subscriber_page.countSubscribers();
});
it("should check whether the selected no. of subscribers are displayed", function () {
expect(subscriber_page.getSubscribers()).toBe(10);
});
});
});
and the code below is a sample of my page object:
var SubscriberPage = function () {
"use strict";
this.subscriberCount = function() { return element...};
this.selectOption = function(count) {return element...};
var totalSubscribers;
/**
* This method selects items in list box
*/
this.selectItems = function (count) {
this.selectOption(count).click(); //selects the items from list box
};
/**
* This method sets the number of subscribers listed
*/
this.countSubscribers = function () {
this.subscriberCount().count().then(function (count) {
totalSubscribers = count;
});
};
/**
* This method returns the subscriber count
*/
this.getSubscribers = function () {
return totalSubscribers;//returns the count of subscribers.
};
};
module.exports = new SubscriberPage();
The code select items successfully, but it makes long pause afterward then produce
following error message:
Failed: Timed out waiting for Protractor to synchronize with the page after 5 seconds. Please see https://github.com/angular/protractor/blob/master/docs/faq.md
While waiting for element with locator - Locator: By(css selector, ...)
What I found out so far:
If I comment out following line from spec page countSubscribers method rather works correctly:
subscriber_page.selectItems(selectedCount);
or if I temporarily flip the ignoreSynchronization variable to true before countSubscribers method is executed, it works fine.
Can anyone explain what the protractor is doing at this point and what the best solution is while browser.ignoreSynchronization is set to false?
Keep a reference of "this" before your function. And then call it whenever you want.
Also if you do element.all(...) You'll be able to use the native .count() which will resolve the promise for you.
//Reference to this
var self = this;
//element.all with .count()
this.subscriberCount = function() { return element.all(...).Count()};
//This method returns the number of subscribers listed
this.countSubscribers = function () {
return self.subscriberCount();
});